Support for NSS as a libpq TLS backend
The attached patch implements NSS (Network Security Services) [0]https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS with the
required NSPR runtime [1]https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR as a TLS backend for PostgreSQL.
While all sslmodes are implemented and work for the most part, the patch is
*not* ready yet but I wanted to show progress early so that anyone interested
in this can help out with testing and maybe even hacking.
Why NSS? Well. It shares no lineage with OpenSSL making it not just an
alternative by fork but a 100% alternative. It's also actively maintained, is
readily available on many platforms where PostgreSQL is popular and has a FIPS
mode which doesn't require an EOL'd library. And finally, I was asked nicely
with the promise of a free beverage, an incentive as good as any.
Differences with OpenSSL
------------------------
NSS does not use certificates and keys on the filesystem, it instead uses a
certificate database in which all certificates, keys and CRL's are loaded. A
set of tools are provided to work with the database, like: certutil, crlutil,
pk12util etc. We could support plain PEM files as well, and load them into a
database ourselves but so far I've opted for just using what is already in the
database.
This does mean that new GUCs are needed to identify the database. I've mostly
repurposed the existing ones for cert/key/crl, but had to invent a new one for
the database. Maybe there should be an entirely new set? This needs to be
discussed with not only NSS in mind but for additional as-of-yet unknown
backends we might get (SChannel comes to mind).
NSS also supports partial chain validation per default (as do many other TLS
libraries) where OpenSSL does not. I haven't done anything about that just
yet, thus there is a failing test as a reminder to address it.
The documentation of NSS/NSPR is unfortunately quite poor and often times
outdated or simply nonexisting. Cloning the repo and reading the source code
is the only option for parts of the API.
Featurewise there might be other things we can make use of in NSS which doesn't
exist in OpenSSL, but for now I've tried to keep them aligned.
Known Bugs and Limitations (in this version of the patch)
---------------------------------------------------------
The frontend doesn't attempt to verify whether the specified CRL exists in the
database or not. This can be done with pretty much the same code as in the
backend, except that we don't have the client side certificate loaded so we
either need to read it back from the database, or parse a list of all CRLs
(which would save us from having the cert in local memory which generally is a
good thing to avoid).
pgtls_read is calling PR_Recv which works fine for communicating with an NSS
backend cluster, but hangs waiting for IO when communicating with an OpenSSL
backend cluster. Using PR_Read reverses the situation. This is probably a
simple bug but I haven't had time to track it down yet. The below shifts
between the two for debugging.
- nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+ nread = PR_Read(conn->pr_fd, ptr, len);
Passphrase handling in the backend is broken, more on that under TODO.
There are a few failing tests and a few skipped ones for now, but the majority
of the tests pass.
Testing
-------
In order for the TAP framework to be able to handle backends with different
characteristics I've broken up SSLServer.pm into a set of modules:
SSL::Server
SSL::Backend::NSS
SSL::Backend::OpenSSL
The SSL tests import SSL::Server which in turn imports the appropriate backend
module in order to perform backend specific setup tasks. The backend used
should be transparent for the TAP code when it comes to switching server certs
etc.
So far I've used foo|bar in the matching regex to provide alternative output,
and SKIP blocks for tests that don't apply. There might be neater ways to
achieve this, but I was trying to avoid having separate test files for the
different backends.
The certificate databases can be created with a new nssfiles make target in
src/test/ssl, which use the existing files (and also depend on OpenSSL which I
don't think is a problematic dependency for development environments). To keep
it simple I've named the certificates in the NSS database after the filenames,
this isn't really NSS best-practices but it makes for an easier time reading
the tests IMO.
If this direction is of interest, extracting into to a separate patch for just
setting up the modules and implementing OpenSSL without a new backend is
probably the next step.
TODO
----
This patch is a work in progress, and there is work left to do, below is a dump
of what is left to fix before this can be considered a full implementation for
review. Most of these items have more documentation in the code comments.
* The split between init and open needs to be revisited, especially in frontend
where we have a bit more freedom. It remains to be seen if we can do better in
the backend part.
* Documentation, it's currently not even started
* Windows support. I've hacked mostly using Debian and have tested versions of
the patch on macOS, but not Windows so far.
* Figure out how to handle cipher configuration. Getting a set of ciphers that
result in a useable socket isn't as easy as with OpenSSL, and policies seem
much more preferred. At the very least this needs to be solidly documented.
* The rules in src/test/ssl/Makefile for generating certificate databases can
probably be generalized into a smaller set of rules based on wildcards.
* The password callback on server-side won't be invoked at server start due to
init happening in be_tls_open, so something needs to be figured out there.
Maybe attempt to open the database with a throw-away context in init just to
invoke the password callback?
* Identify code duplicated between frontend and backend and try to generalize.
* Make sure the handling the error codes correctly in the certificate and auth
callbacks to properly handle self-signed certs etc.
* Tidy up the tests which are partially hardwired for NSS now to make sure
there are no regressions for OpenSSL.
* All the code using OpenSSL which isn't the libpq communications parts, like
pgcrypto, strong_random, sha2, SCRAM et.al
* Review language in code comments and run pgindent et.al
* Settle on a minimum required version. I've been using NSS 3.42 and NSPR 4.20
simply since they were the packages Debian wanted to install for me, but I'm
quite convinced that we can go a bit lower (featurewise we can go much lower
but there are bugfixes in recent versions that we might want to include).
Anything lower than a version supporting TLSv1.3 seems like an obvious no-no.
I'd be surprised if this is all, but that's at least a start. There isn't
really a playbook on how to add a new TLS backend, but I'm hoping to be able to
summarize the required bits and pieces in README.SSL once this is a bit closer
to completion.
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).
The 0001 patch contains the full NSS support, and 0002 is a fix for the pgstat
abstraction which IMO leaks backend implementation details. This needs to go
on it's own thread, but since 0001 fails without it I've included it here for
simplicity sake for now.
cheers ./daniel
[0]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS
[1]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend.patchapplication/octet-stream; name=0001-WIP-Support-libnss-for-as-TLS-backend.patch; x-unix-mode=0644Download
From 8be8a796e941c5483a149ca4fe0345d24461b57c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 4 May 2020 10:48:37 +0200
Subject: [PATCH 1/2] WIP: Support libnss for as TLS backend
---
configure | 235 +++-
configure.in | 30 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1024 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-nss.c | 960 ++++++++++++++++
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/ssl/Makefile | 157 +++
src/test/ssl/t/001_ssltests.pl | 199 ++--
src/test/ssl/t/002_scram.pl | 2 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 72 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 98 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 60 +-
24 files changed, 2934 insertions(+), 125 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (82%)
diff --git a/configure b/configure
index 6f219d9163..694f77d0cb 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -798,6 +799,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -856,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -935,6 +938,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1191,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1337,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1490,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1558,6 +1572,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8009,6 +8024,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12083,6 +12133,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
@@ -12341,6 +12394,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13243,6 +13447,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14588,7 +14811,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14634,7 +14857,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14658,7 +14881,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14703,7 +14926,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14727,7 +14950,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
diff --git a/configure.in b/configure.in
index f9f9b320f2..65743a36d2 100644
--- a/configure.in
+++ b/configure.in
@@ -854,6 +854,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1203,6 +1212,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
if test "$PORTNAME" != "win32"; then
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for OpenSSL])])
@@ -1225,6 +1237,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1400,6 +1425,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..1f1706c4d0 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..9f746e0016
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1024 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct {
+ enum {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and postgres"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will make
+ * the forked child look it up by the default name SSL_INHERITANCE, if env
+ * vars aren't inherited then the contents of the variable can be passed
+ * instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5
+ * seconds, so opt for that in both cases (the defaults being 100
+ * seconds and 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = { PW_NONE, 0 }; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */);
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database.
+ * If the certificate path isn't a valid directory, NSS will fall back on
+ * the system certificate database. If the certificate path is a directory
+ * but is empty then the initialization will fail. On the client side this
+ * can be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562
+ * For the server side we wont allow this to fail however, as we require
+ * the certificate and key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites, set
+ * the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers, *c, *b;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok_r(ciphers, sep, &b); c; c = strtok_r(NULL, sep, &b))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */);
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */);
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that
+ * we can selectively override the ones we want while still ensuring that
+ * we have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch(v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value zero,
+ * so we use this to signal the caller that the highest useful version
+ * should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+ /*
+ * No guard is required here as there are no versions of NSS without
+ * support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2f3e0a70e0..080699761f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4252,7 +4252,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4310,6 +4314,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_MASTER,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4338,8 +4354,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..3a0e9cb453
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..fc2f16d847 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ //void *ssl;
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c199cd46d2..dba1443758 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -889,6 +889,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8f3ec6bde1..2a5b59b998 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d5da6dce1e..91a6dccc37 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_NSS)
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+#endif
+
/* 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..b707c0f8ea
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,960 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and PostgreSQL"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+static NSSInitContext *nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_type_nss PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ //status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust");
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which copes
+ * with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access information
+ * on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch(channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_type_nss
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in retrying
+ * as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ea13f5afb8..e9bf5b5055 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_type PQgetSSLKeyPassHook(void);
extern void PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook);
extern int PQdefaultSSLKeyPassHook(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_type_nss)(PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_type_nss PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss 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 1de91ae295..53602dc1a6 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -485,6 +485,11 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ char *cert_database;
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 06e37d5269..968d7c4b85 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,27 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/both-cas-1.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.der \
+ ssl/nss/server.crl.der \
+ ssl/nss/client.crl.der \
+ 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.der \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +59,9 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +89,11 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +107,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-only.crt__server-password.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+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 server-cn-only.crt__server-cn-only.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-multiple-alt-names.crt__server-multiple-alt-names.key -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-single-alt-name.crt__server-single-alt-name.key -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-and-alt-names.crt__server-cn-and-alt-names.key -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-no-names.crt__server-no-names.key -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -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 server-revoked.crt__server-revoked.key -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -des -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +186,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -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 client.crt__client-encrypted-pem.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +214,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client-revoked.crt__client-revoked.key -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -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
@@ -112,6 +239,13 @@ ssl/client-encrypted-der.key: ssl/client.key
ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
cat $^ > $@
+ssl/nss/both-cas-1.crt.db: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ 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"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+
# The same, but the certs are in different order
ssl/both-cas-2.crt: ssl/root_ca.crt ssl/server_ca.crt ssl/client_ca.crt
cat $^ > $@
@@ -127,19 +261,41 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client+client_ca.crt__client.key -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl.der: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl.der: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl.der: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl.der: 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
@@ -154,6 +310,7 @@ sslfiles-clean:
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a454bb0274..53914b0ad3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,30 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-
#### Set up the server.
note "setting up data directory";
@@ -70,32 +53,27 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1 if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -147,44 +125,44 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=require");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
# Try with just the server CA's cert. This fails because the root file
# must contain the whole chain up to the root CA.
test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca cert_database=ssl/nss/server_ca.crt.db",
qr/SSL error/, "connect with server CA cert, without root CA");
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
# Test with cert root file that contains two certificates. The client should
@@ -193,36 +171,42 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
"cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+# How about import the both-file into a database?
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases ", 1 if ($nss);
+
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"CRL belonging to a different CA");
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -235,14 +219,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,12 +244,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -273,7 +257,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -283,12 +267,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -297,7 +281,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -310,14 +294,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -326,7 +310,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -338,11 +322,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/nss/server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -363,21 +347,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -388,7 +372,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -400,36 +384,47 @@ test_connect_fails(
# correct client cert in unencrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"certificate authorization succeeds with correct client cert in PEM format"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in unencrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
"certificate authorization succeeds with correct client cert in DER format"
);
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in encrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
"certificate authorization succeeds with correct client cert in encrypted DER format"
);
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -469,18 +464,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,CN=ssltestuser,1,\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -492,15 +488,18 @@ SKIP:
# client cert belonging to another user
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=client-revoked.crt__client-revoked.key sslkey=ssl/client-revoked_tmp.key",
qr/SSL error/,
"certificate authorization fails with revoked client cert");
@@ -508,17 +507,17 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-full succeeds with matching username and Common Name"
);
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
qr/FATAL/,
"auth_option clientcert=verify-full fails with mismatching username and Common Name"
);
@@ -527,24 +526,22 @@ test_connect_fails(
# works, when username doesn't match Common Name
test_connect_ok(
$common_connstr,
- "user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=yetanotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "sslmode=require sslcert=client+client_ca.crt__client.key",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
+test_connect_fails($common_connstr, "sslmode=require sslcert=client.crt__client.key",
qr/SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index ee6e26d732..d7b7c67d7a 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,7 +11,7 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
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..987e5b3cfe
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,72 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(
+ get_new_backend
+ set_server_cert
+);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'NSS'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ # "ssl_crl_file='root+client.crl'\n" .
+
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$cert_nickname'\n" .
+ "ssl_crl_file=''\n" .
+ "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..1fd1db2a8d
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,98 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+
+our @EXPORT = qw(
+ get_new_backend
+);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'OpenSSL'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$certfile.crt'\n" .
+ "ssl_key_file='$keyfile.key'\n" .
+ "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 82%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..02849f4b8a 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,16 +24,35 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ use SSL::Backend::OpenSSL qw(get_new_backend set_server_cert);
+ $backend = get_new_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ use SSL::Backend::NSS qw(get_new_backend set_server_cert);
+ $backend = get_new_backend();
+ $nss = 1;
+}
+
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -161,6 +187,16 @@ sub configure_test_server_for_ssl
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +204,16 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n";
close $sslconf;
$node->restart;
--
2.21.1 (Apple Git-122.3)
0002-Make-pg_stat_ssl-reporting-backend-agnostic.patchapplication/octet-stream; name=0002-Make-pg_stat_ssl-reporting-backend-agnostic.patch; x-unix-mode=0644Download
From fba2564e8881d5c138bd49f44f7ed2cdfe69c21a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 16 Apr 2020 07:38:28 +0200
Subject: [PATCH 2/2] Make pg_stat_ssl reporting backend agnostic
Inspecting Port->ssl for an indication on whether the connection is
using TLS or not is tied to the fact that the current implementation
is using a variable named ssl. Making this a requirement for all TLS
backend implementations seems restricting since there in actual var
tracking the status, ssl_in_use. Switch to inspecting this variable
instead.
---
src/backend/postmaster/pgstat.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 5fc35ded19..bc8ebab7d2 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2993,7 +2993,7 @@ pgstat_bestart(void)
MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));
#ifdef USE_SSL
- if (MyProcPort && MyProcPort->ssl != NULL)
+ if (MyProcPort && MyProcPort->ssl_in_use)
{
lbeentry.st_ssl = true;
lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort);
--
2.21.1 (Apple Git-122.3)
On 15 May 2020, at 22:46, Daniel Gustafsson <daniel@yesql.se> wrote:
The 0001 patch contains the full NSS support, and 0002 is a fix for the pgstat
abstraction which IMO leaks backend implementation details. This needs to go
on it's own thread, but since 0001 fails without it I've included it here for
simplicity sake for now.
The attached 0001 and 0002 are the same patchseries as before, but with the
OpenSSL test module fixed and a rebase on top of the current master.
cheers ./daniel
Attachments:
0002-Make-pg_stat_ssl-reporting-backend-agnostic-v2.patchapplication/octet-stream; name=0002-Make-pg_stat_ssl-reporting-backend-agnostic-v2.patch; x-unix-mode=0644Download
From 3642ad3ebcd959f6a1560516369b5a87ebcd214f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 25 Jun 2020 17:31:35 +0200
Subject: [PATCH 2/2] Make pg_stat_ssl reporting backend agnostic v2
Inspecting Port->ssl for an indication on whether the connection is
using TLS or not is tied to the fact that the current implementation
is using a variable named ssl. Making this a requirement for all TLS
backend implementations seems restricting since there in actual var
tracking the status, ssl_in_use. Switch to inspecting this variable
instead.
---
src/backend/postmaster/pgstat.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index c022597bc0..edfa774ee4 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2989,7 +2989,7 @@ pgstat_bestart(void)
MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));
#ifdef USE_SSL
- if (MyProcPort && MyProcPort->ssl != NULL)
+ if (MyProcPort && MyProcPort->ssl_in_use)
{
lbeentry.st_ssl = true;
lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort);
--
2.21.1 (Apple Git-122.3)
0001-WIP-Support-libnss-for-as-TLS-backend-v2.patchapplication/octet-stream; name=0001-WIP-Support-libnss-for-as-TLS-backend-v2.patch; x-unix-mode=0644Download
From ceec1b744171d4760de7cb0336a7d78a13dbbf6b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 25 Jun 2020 17:31:28 +0200
Subject: [PATCH 1/2] WIP: Support libnss for as TLS backend v2
---
configure | 235 +++-
configure.in | 30 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1026 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-nss.c | 960 +++++++++++++++
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/ssl/Makefile | 157 +++
src/test/ssl/t/001_ssltests.pl | 201 ++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 65 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 97 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 60 +-
24 files changed, 2930 insertions(+), 127 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (82%)
diff --git a/configure b/configure
index 2feff37fe3..3f6520ae34 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -798,6 +799,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -856,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -935,6 +938,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1191,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1337,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1490,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1558,6 +1572,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8009,6 +8024,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12083,6 +12133,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
@@ -12341,6 +12394,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13243,6 +13447,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14588,7 +14811,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14634,7 +14857,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14658,7 +14881,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14703,7 +14926,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14727,7 +14950,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
diff --git a/configure.in b/configure.in
index 0188c6ff07..afa90f7fbc 100644
--- a/configure.in
+++ b/configure.in
@@ -854,6 +854,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1203,6 +1212,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
if test "$PORTNAME" != "win32"; then
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for OpenSSL])])
@@ -1225,6 +1237,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1400,6 +1425,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..1f1706c4d0 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..abb0b27d18
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1026 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and postgres"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will make
+ * the forked child look it up by the default name SSL_INHERITANCE, if env
+ * vars aren't inherited then the contents of the variable can be passed
+ * instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5
+ * seconds, so opt for that in both cases (the defaults being 100
+ * seconds and 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = { PW_NONE, 0 }; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */);
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database.
+ * If the certificate path isn't a valid directory, NSS will fall back on
+ * the system certificate database. If the certificate path is a directory
+ * but is empty then the initialization will fail. On the client side this
+ * can be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562
+ * For the server side we wont allow this to fail however, as we require
+ * the certificate and key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites, set
+ * the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers, *c, *b;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok_r(ciphers, sep, &b); c; c = strtok_r(NULL, sep, &b))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */);
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */);
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that
+ * we can selectively override the ones we want while still ensuring that
+ * we have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch(v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value zero,
+ * so we use this to signal the caller that the highest useful version
+ * should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+ /*
+ * No guard is required here as there are no versions of NSS without
+ * support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 75fc6f11d6..6153745f07 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4233,7 +4233,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4291,6 +4295,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_MASTER,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4319,8 +4335,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..3a0e9cb453
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..fc2f16d847 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ //void *ssl;
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c199cd46d2..dba1443758 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -889,6 +889,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8f3ec6bde1..2a5b59b998 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2c87b34028..848132976c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_NSS)
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+#endif
+
/* 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..b707c0f8ea
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,960 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and PostgreSQL"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+static NSSInitContext *nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_type_nss PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ //status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust");
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which copes
+ * with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access information
+ * on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch(channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_type_nss
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in retrying
+ * as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..1fc7e43cfd 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_type_nss)(PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_type_nss PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss 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 1de91ae295..53602dc1a6 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -485,6 +485,11 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ char *cert_database;
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..bb54939ead 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,27 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/both-cas-1.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.der \
+ ssl/nss/server.crl.der \
+ ssl/nss/client.crl.der \
+ 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.der \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +59,9 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +89,11 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +107,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-only.crt__server-password.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+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 server-cn-only.crt__server-cn-only.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-multiple-alt-names.crt__server-multiple-alt-names.key -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-single-alt-name.crt__server-single-alt-name.key -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-and-alt-names.crt__server-cn-and-alt-names.key -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-no-names.crt__server-no-names.key -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -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 server-revoked.crt__server-revoked.key -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +186,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -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 client.crt__client-encrypted-pem.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +214,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client-revoked.crt__client-revoked.key -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -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
@@ -112,6 +239,13 @@ ssl/client-encrypted-der.key: ssl/client.key
ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
cat $^ > $@
+ssl/nss/both-cas-1.crt.db: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ 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"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+
# The same, but the certs are in different order
ssl/both-cas-2.crt: ssl/root_ca.crt ssl/server_ca.crt ssl/client_ca.crt
cat $^ > $@
@@ -127,19 +261,41 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client+client_ca.crt__client.key -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl.der: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl.der: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl.der: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl.der: 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
@@ -154,6 +310,7 @@ sslfiles-clean:
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a454bb0274..dbbe94a645 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,30 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-
#### Set up the server.
note "setting up data directory";
@@ -70,32 +53,27 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1 if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -147,44 +125,44 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=require");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
# Try with just the server CA's cert. This fails because the root file
# must contain the whole chain up to the root CA.
test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca cert_database=ssl/nss/server_ca.crt.db",
qr/SSL error/, "connect with server CA cert, without root CA");
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
# Test with cert root file that contains two certificates. The client should
@@ -193,36 +171,42 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
"cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+# How about import the both-file into a database?
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases ", 1 if ($nss);
+
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"CRL belonging to a different CA");
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -235,14 +219,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,12 +244,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -273,7 +257,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -283,12 +267,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -297,7 +281,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -310,14 +294,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -326,7 +310,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -338,11 +322,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/nss/server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -363,21 +347,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -388,7 +372,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -400,36 +384,47 @@ test_connect_fails(
# correct client cert in unencrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"certificate authorization succeeds with correct client cert in PEM format"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in unencrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
"certificate authorization succeeds with correct client cert in DER format"
);
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in encrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
"certificate authorization succeeds with correct client cert in encrypted DER format"
);
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -469,18 +464,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,CN=ssltestuser,1,\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -492,15 +488,18 @@ SKIP:
# client cert belonging to another user
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=client-revoked.crt__client-revoked.key sslkey=ssl/client-revoked_tmp.key",
qr/SSL error/,
"certificate authorization fails with revoked client cert");
@@ -508,17 +507,17 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-full succeeds with matching username and Common Name"
);
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
qr/FATAL/,
"auth_option clientcert=verify-full fails with mismatching username and Common Name"
);
@@ -527,24 +526,22 @@ test_connect_fails(
# works, when username doesn't match Common Name
test_connect_ok(
$common_connstr,
- "user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=yetanotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "sslmode=require sslcert=client+client_ca.crt__client.key",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+test_connect_fails($common_connstr, "sslmode=require sslcert=client.crt__client.key",
+ qr/connection requires a valid client certificate|SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index ee6e26d732..2546c4bb6a 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
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..2b44831739
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,65 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'NSS'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$cert_nickname'\n" .
+ "ssl_crl_file=''\n" .
+ "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..757342d748
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,97 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'OpenSSL'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$certfile.crt'\n" .
+ "ssl_key_file='$keyfile.key'\n" .
+ "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 82%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..9208946279 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -161,6 +187,16 @@ sub configure_test_server_for_ssl
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +204,16 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n";
close $sslconf;
$node->restart;
--
2.21.1 (Apple Git-122.3)
On 25 Jun 2020, at 17:39, Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 May 2020, at 22:46, Daniel Gustafsson <daniel@yesql.se> wrote:
The 0001 patch contains the full NSS support, and 0002 is a fix for the pgstat
abstraction which IMO leaks backend implementation details. This needs to go
on it's own thread, but since 0001 fails without it I've included it here for
simplicity sake for now.The attached 0001 and 0002 are the same patchseries as before, but with the
OpenSSL test module fixed and a rebase on top of the current master.
Another rebase to resolve conflicts with the recent fixes in the SSL tests, as
well as some minor cleanup.
cheers ./daniel
Attachments:
0002-Make-pg_stat_ssl-reporting-backend-agnostic-v3.patchapplication/octet-stream; name=0002-Make-pg_stat_ssl-reporting-backend-agnostic-v3.patch; x-unix-mode=0644Download
From b2c489f9715d647f709ddcb888ec1d64c231ddca Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 3 Jul 2020 13:42:21 +0200
Subject: [PATCH 2/2] Make pg_stat_ssl reporting backend agnostic v3
Inspecting Port->ssl for an indication on whether the connection is
using TLS or not is tied to the fact that the current implementation
is using a variable named ssl. Making this a requirement for all TLS
backend implementations seems restricting since there in actual var
tracking the status, ssl_in_use. Switch to inspecting this variable
instead.
---
src/backend/postmaster/pgstat.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index c022597bc0..edfa774ee4 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2989,7 +2989,7 @@ pgstat_bestart(void)
MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));
#ifdef USE_SSL
- if (MyProcPort && MyProcPort->ssl != NULL)
+ if (MyProcPort && MyProcPort->ssl_in_use)
{
lbeentry.st_ssl = true;
lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort);
--
2.21.1 (Apple Git-122.3)
0001-WIP-Support-libnss-for-as-TLS-backend-v3.patchapplication/octet-stream; name=0001-WIP-Support-libnss-for-as-TLS-backend-v3.patch; x-unix-mode=0644Download
From 20531c638da852348c629f2e2d2f6fbb2014f2c6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 3 Jul 2020 13:34:27 +0200
Subject: [PATCH 1/2] WIP: Support libnss for as TLS backend v3
---
configure | 235 +++-
configure.in | 30 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1026 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-nss.c | 959 +++++++++++++++
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/ssl/Makefile | 157 +++
src/test/ssl/t/001_ssltests.pl | 203 ++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 65 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 102 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 60 +-
24 files changed, 2934 insertions(+), 129 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (82%)
diff --git a/configure b/configure
index 2feff37fe3..3f6520ae34 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -798,6 +799,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -856,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -935,6 +938,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1191,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1337,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1490,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1558,6 +1572,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8009,6 +8024,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12083,6 +12133,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
@@ -12341,6 +12394,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13243,6 +13447,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14588,7 +14811,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14634,7 +14857,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14658,7 +14881,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14703,7 +14926,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14727,7 +14950,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
diff --git a/configure.in b/configure.in
index 0188c6ff07..afa90f7fbc 100644
--- a/configure.in
+++ b/configure.in
@@ -854,6 +854,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1203,6 +1212,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
if test "$PORTNAME" != "win32"; then
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for OpenSSL])])
@@ -1225,6 +1237,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1400,6 +1425,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..1f1706c4d0 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..07df6c2752
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1026 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and postgres"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will make
+ * the forked child look it up by the default name SSL_INHERITANCE, if env
+ * vars aren't inherited then the contents of the variable can be passed
+ * instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5
+ * seconds, so opt for that in both cases (the defaults being 100
+ * seconds and 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = { PW_NONE, 0 }; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */);
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database.
+ * If the certificate path isn't a valid directory, NSS will fall back on
+ * the system certificate database. If the certificate path is a directory
+ * but is empty then the initialization will fail. On the client side this
+ * can be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562
+ * For the server side we wont allow this to fail however, as we require
+ * the certificate and key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites, set
+ * the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers, *c, *b;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok_r(ciphers, sep, &b); c; c = strtok_r(NULL, sep, &b))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */);
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */);
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that
+ * we can selectively override the ones we want while still ensuring that
+ * we have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch(v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value zero,
+ * so we use this to signal the caller that the highest useful version
+ * should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+ /*
+ * No guard is required here as there are no versions of NSS without
+ * support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 75fc6f11d6..6153745f07 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4233,7 +4233,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4291,6 +4295,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_MASTER,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4319,8 +4335,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..38249314d5
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..fc2f16d847 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ //void *ssl;
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c199cd46d2..dba1443758 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -889,6 +889,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8f3ec6bde1..2a5b59b998 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 27c9bb46ee..8dd1eb9e5c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_NSS)
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+#endif
+
/* 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..863c58f20b
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,959 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and PostgreSQL"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+static NSSInitContext *nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_type_nss PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ //status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust");
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which copes
+ * with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access information
+ * on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch(channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_type_nss
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in retrying
+ * as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..1fc7e43cfd 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_type_nss)(PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_type_nss PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss 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 1de91ae295..53602dc1a6 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -485,6 +485,11 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ char *cert_database;
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..aa68b4175c 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,27 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/both-cas-1.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.der \
+ ssl/nss/server.crl.der \
+ ssl/nss/client.crl.der \
+ 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.der \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +59,9 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +89,11 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +107,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-only.crt__server-password.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+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 server-cn-only.crt__server-cn-only.key -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-multiple-alt-names.crt__server-multiple-alt-names.key -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-single-alt-name.crt__server-single-alt-name.key -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-cn-and-alt-names.crt__server-cn-and-alt-names.key -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n server-no-names.crt__server-no-names.key -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -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 server-revoked.crt__server-revoked.key -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +186,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -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 client.crt__client-encrypted-pem.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +214,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client-revoked.crt__client-revoked.key -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -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
@@ -112,6 +239,13 @@ ssl/client-encrypted-der.key: ssl/client.key
ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
cat $^ > $@
+ssl/nss/both-cas-1.crt.db: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ 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"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+
# The same, but the certs are in different order
ssl/both-cas-2.crt: ssl/root_ca.crt ssl/server_ca.crt ssl/client_ca.crt
cat $^ > $@
@@ -127,19 +261,41 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n client+client_ca.crt__client.key -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client.crt__client.key -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl.der: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl.der: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl.der: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl.der: 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
@@ -154,6 +310,7 @@ sslfiles-clean:
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index c0680f39d6..dbbe94a645 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,27 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1 if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -149,44 +125,44 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=require");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
# Try with just the server CA's cert. This fails because the root file
# must contain the whole chain up to the root CA.
test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca cert_database=ssl/nss/server_ca.crt.db",
qr/SSL error/, "connect with server CA cert, without root CA");
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
# Test with cert root file that contains two certificates. The client should
@@ -195,36 +171,42 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
"cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+# How about import the both-file into a database?
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases ", 1 if ($nss);
+
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"CRL belonging to a different CA");
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +219,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +244,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +257,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +267,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +281,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +294,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +310,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +322,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/nss/server.crl.der cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +347,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +372,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -402,36 +384,47 @@ test_connect_fails(
# correct client cert in unencrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"certificate authorization succeeds with correct client cert in PEM format"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in unencrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
"certificate authorization succeeds with correct client cert in DER format"
);
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in encrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
"certificate authorization succeeds with correct client cert in encrypted DER format"
);
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=client.crt__client-encrypted-pem.key sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +464,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,CN=ssltestuser,1,\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -494,15 +488,18 @@ SKIP:
# client cert belonging to another user
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=client-revoked.crt__client-revoked.key sslkey=ssl/client-revoked_tmp.key",
qr/SSL error/,
"certificate authorization fails with revoked client cert");
@@ -510,17 +507,17 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=ssltestuser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-full succeeds with matching username and Common Name"
);
test_connect_fails(
$common_connstr,
- "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=anotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
qr/FATAL/,
"auth_option clientcert=verify-full fails with mismatching username and Common Name"
);
@@ -529,24 +526,22 @@ test_connect_fails(
# works, when username doesn't match Common Name
test_connect_ok(
$common_connstr,
- "user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "user=yetanotheruser sslcert=client.crt__client.key sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "sslmode=require sslcert=client+client_ca.crt__client.key",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+test_connect_fails($common_connstr, "sslmode=require sslcert=client.crt__client.key",
+ qr/connection requires a valid client certificate|SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a1ab911988..d83bc8658b 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
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..2b44831739
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,65 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'NSS'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$cert_nickname'\n" .
+ "ssl_crl_file=''\n" .
+ "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..3d53289196
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,102 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'OpenSSL'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$certfile.crt'\n" .
+ "ssl_key_file='$keyfile.key'\n" .
+ "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 82%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..8e970833b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -161,6 +187,16 @@ sub configure_test_server_for_ssl
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +204,16 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n";
close $sslconf;
$node->restart;
--
2.21.1 (Apple Git-122.3)
On Fri, Jul 3, 2020 at 11:51 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 25 Jun 2020, at 17:39, Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 May 2020, at 22:46, Daniel Gustafsson <daniel@yesql.se> wrote:
The 0001 patch contains the full NSS support, and 0002 is a fix for the pgstat
abstraction which IMO leaks backend implementation details. This needs to go
on it's own thread, but since 0001 fails without it I've included it here for
simplicity sake for now.The attached 0001 and 0002 are the same patchseries as before, but with the
OpenSSL test module fixed and a rebase on top of the current master.Another rebase to resolve conflicts with the recent fixes in the SSL tests, as
well as some minor cleanup.
Hi Daniel,
Thanks for blazing the trail for other implementations to coexist in
the tree. I see that cURL (another project Daniel works on)
supports a lot of TLS implementations[1]https://curl.haxx.se/docs/ssl-compared.html. I recognise 4 other library
names from that table as having appeared on this mailing list as
candidates for PostgreSQL support complete with WIP patches, including
another one from you (Apple Secure Transport). I don't have strong
views on how many and which libraries we should support, but I was
curious how many packages depend on libss1.1, libgnutls30 and libnss3
in the Debian package repos in my sources.list, and I came up with
OpenSSL = 820, GnuTLS = 342, and NSS = 87.
I guess Solution.pm needs at least USE_NSS => undef for this not to
break the build on Windows.
Obviously cfbot is useless for testing this code, since its build
script does --with-openssl and you need --with-nss, but it still shows
us one thing: with your patch, a --with-openssl build is apparently
broken:
/001_ssltests.pl .. 1/93 Bailout called. Further testing stopped:
system pg_ctl failed
There are some weird configure-related hunks in the patch:
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
...[more stuff like that]...
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
I see the same when I use Debian's autoconf, but not FreeBSD's or
MacPorts', despite all being version 2.69. That seems to be due to
non-upstreamed changes added by the Debian maintainers (I see the
off_t thing mentioned in /usr/share/doc/autoconf/changelog.Debian.gz).
I think you need to build a stock autoconf 2.69 or run autoconf on a
non-Debian system.
I installed libnss3-dev on my Debian box and then configure had
trouble locating and understanding <ssl.h>, until I added
--with-includes=/usr/include/nss:/usr/include/nspr. I suspect this is
supposed to be done with pkg-config nss --cflags somewhere in
configure (or alternatively nss-config --cflags, nspr-config --cflags,
I don't know, but we're using pkg-config for other stuff).
I installed the Debian package libnss3-tools (for certutil) and then,
in src/test/ssl, I ran make nssfiles (I guess that should be
automatic?), and make check, and I got this far:
Test Summary Report
-------------------
t/001_ssltests.pl (Wstat: 3584 Tests: 93 Failed: 14)
Failed tests: 14, 16, 18-20, 24, 27-28, 54-55, 78-80
91
Non-zero exit status: 14
You mentioned some were failing in this WIP -- are those results you expect?
On 10 Jul 2020, at 07:10, Thomas Munro <thomas.munro@gmail.com> wrote:
On Fri, Jul 3, 2020 at 11:51 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 25 Jun 2020, at 17:39, Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 May 2020, at 22:46, Daniel Gustafsson <daniel@yesql.se> wrote:
The 0001 patch contains the full NSS support, and 0002 is a fix for the pgstat
abstraction which IMO leaks backend implementation details. This needs to go
on it's own thread, but since 0001 fails without it I've included it here for
simplicity sake for now.The attached 0001 and 0002 are the same patchseries as before, but with the
OpenSSL test module fixed and a rebase on top of the current master.Another rebase to resolve conflicts with the recent fixes in the SSL tests, as
well as some minor cleanup.Hi Daniel,
Thanks for blazing the trail for other implementations to coexist in
the tree. I see that cURL (another project Daniel works on)
supports a lot of TLS implementations[1].
The list on that URL is also just a selection, the total count is 10 (not
counting OpenSSL forks) IIRC, after axing support for a few lately. OpenSSL
clearly has a large mindshare but the gist of it is that there exist quite a
few alternatives each with their own strengths.
I recognise 4 other library
names from that table as having appeared on this mailing list as
candidates for PostgreSQL support complete with WIP patches, including
another one from you (Apple Secure Transport). I don't have strong
views on how many and which libraries we should support,
I think it's key to keep in mind *why* it's relevant to provide options in the
first place, after all, as they must be 100% interoperable one can easily argue
for a single one being enough. We need to to look at what they offer users on
top of just a TLS connection, like: managed certificate storage like for
example macOS Keychains, FIPS certification, good platform availability and/or
OS integration etc. If all a library offers is "not being OpenSSL" then it's
not clear that we're adding much value by spending the cycles to support it.
My personal opinion is that we should keep it pretty limited, not least to
lessen the burden of testing and during feature development. Supporting a new
library comes with requirements on both the CFBot as well as the buildfarm, not
to mention on developers who dabble in that area of the code. The goal should
IMHO be to make it trivial for every postgres installation to use TLS
regardless of platform and experience level with the person installing it.
The situation is a bit different for curl where we have as a goal to provide
enough alternatives such that every platform can have a libcurl/curl more or
less regardless of what it contains. As a consequence, we have around 80 CI
jobs to test each pull request to provide ample coverage. Being a kitchen-
sink is really hard work.
but I was
curious how many packages depend on libss1.1, libgnutls30 and libnss3
in the Debian package repos in my sources.list, and I came up with
OpenSSL = 820, GnuTLS = 342, and NSS = 87.
I don't see a lot of excitement over GnuTLS lately, but Debian shipping it due
to (I believe) licensing concerns with OpenSSL does help it along. In my
experience, platforms with GnuTLS easily available also have OpenSSL easily
available.
I guess Solution.pm needs at least USE_NSS => undef for this not to
break the build on Windows.
Thanks, I'll fix (I admittedly haven't tried this at all on Windows yet).
Obviously cfbot is useless for testing this code, since its build
script does --with-openssl and you need --with-nss,
Right, this is a CFBot problem with any patch that require specific autoconf
flags to be excercised. I wonder if we can make something when we do CF app
integration which can inject flags to a Travis pipeline in a safe manner?
but it still shows
us one thing: with your patch, a --with-openssl build is apparently
broken:/001_ssltests.pl .. 1/93 Bailout called. Further testing stopped:
system pg_ctl failed
Humm .. I hate to say "it worked on my machine" but it did, but my TLS
environment is hardly standard. Sorry for posting breakage, most likely this
is a bug in the new test module structure that the patch introduce in order to
support multiple backends for src/tests/ssl. I'll fix.
There are some weird configure-related hunks in the patch:
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
...[more stuff like that]...-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))I see the same when I use Debian's autoconf, but not FreeBSD's or
MacPorts', despite all being version 2.69. That seems to be due to
non-upstreamed changes added by the Debian maintainers (I see the
off_t thing mentioned in /usr/share/doc/autoconf/changelog.Debian.gz).
I think you need to build a stock autoconf 2.69 or run autoconf on a
non-Debian system.
Sigh, yes that's a Debianism that slipped through, again sorry about that.
I installed libnss3-dev on my Debian box and then configure had
trouble locating and understanding <ssl.h>, until I added
--with-includes=/usr/include/nss:/usr/include/nspr. I suspect this is
supposed to be done with pkg-config nss --cflags somewhere in
configure (or alternatively nss-config --cflags, nspr-config --cflags,
I don't know, but we're using pkg-config for other stuff).
Yeah, that's a good point, I should fix that. Having a metric ton of TLS
libraries in various versions around in my environment I've been Stockholm
Syndromed to --with-includes to the point where I didn't even think to run
without it. It should clearly be as easy to use as OpenSSL wrt autoconf.
I installed the Debian package libnss3-tools (for certutil) and then,
in src/test/ssl, I ran make nssfiles (I guess that should be
automatic?)
Yes, it needs to run automatically for NSS builds on make check.
, and make check, and I got this far:
Test Summary Report
-------------------
t/001_ssltests.pl (Wstat: 3584 Tests: 93 Failed: 14)
Failed tests: 14, 16, 18-20, 24, 27-28, 54-55, 78-80
91
Non-zero exit status: 14You mentioned some were failing in this WIP -- are those results you expect?
I'm not on my dev box at the moment, and I don't remember off the cuff, but
that sounds higher than I remember. I wonder if I fat-fingered the regexes in
the last version?
Thanks for taking a look at the patch, I'll fix up the reported issues Monday
at the latest.
cheers ./daniel
On Fri, Jul 10, 2020 at 5:10 PM Thomas Munro <thomas.munro@gmail.com> wrote:
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))I see the same when I use Debian's autoconf, but not FreeBSD's or
MacPorts', despite all being version 2.69. That seems to be due to
non-upstreamed changes added by the Debian maintainers (I see the
off_t thing mentioned in /usr/share/doc/autoconf/changelog.Debian.gz).
By the way, Dagfinn mentioned that these changes were in fact
upstreamed, and happened to be beta-released today[1]https://lists.gnu.org/archive/html/autoconf/2020-07/msg00006.html, and are due out
in ~3 months as 2.70. That'll be something for us to coordinate a bit
further down the road.
[1]: https://lists.gnu.org/archive/html/autoconf/2020-07/msg00006.html
On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).
I'll be happy to help, particularly with Windows support and with some
of the callback stuff I've had a hand in.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 12 Jul 2020, at 00:03, Daniel Gustafsson <daniel@yesql.se> wrote:
Thanks for taking a look at the patch, I'll fix up the reported issues Monday
at the latest.
A bit of life intervened, but attached is a new version of the patch which
should work for OpenSSL builds, and have the other issues addressed as well. I
took the opportunity to clean up the NSS tests to be more like the OpenSSL ones
to lessen the impact on the TAP testcases. On my Debian box, using the
standard NSS and NSPR packages, I get 6 failures which are essentially all
around CRL handling. I'm going to circle back and look at what is missing there.
This version also removes the required patch for statistics reporting as that
has been committed in 6a5c750f3f72899f4f982f921d5bf5665f55651e.
cheers ./daniel
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend-v4.patchapplication/octet-stream; name=0001-WIP-Support-libnss-for-as-TLS-backend-v4.patch; x-unix-mode=0644Download
From 85115b59e7877285ad00837fe96c09a2f1060093 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 3 Jul 2020 13:34:27 +0200
Subject: [PATCH] WIP: Support libnss for as TLS backend v4
---
configure | 211 ++++
configure.in | 30 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1026 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 959 +++++++++++++++
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/ssl/Makefile | 161 +++
src/test/ssl/t/001_ssltests.pl | 200 ++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 65 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 64 +-
src/tools/msvc/Solution.pm | 5 +
25 files changed, 2930 insertions(+), 118 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (81%)
diff --git a/configure b/configure
index 9907637e31..a799f125ca 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
@@ -12432,6 +12473,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13334,6 +13526,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index 2e05ce2e4d..f6bc04910b 100644
--- a/configure.in
+++ b/configure.in
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
if test "$PORTNAME" != "win32"; then
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for OpenSSL])])
@@ -1227,6 +1239,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1402,6 +1427,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..1f1706c4d0 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..07df6c2752
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1026 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and postgres"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will make
+ * the forked child look it up by the default name SSL_INHERITANCE, if env
+ * vars aren't inherited then the contents of the variable can be passed
+ * instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5
+ * seconds, so opt for that in both cases (the defaults being 100
+ * seconds and 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = { PW_NONE, 0 }; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */);
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database.
+ * If the certificate path isn't a valid directory, NSS will fall back on
+ * the system certificate database. If the certificate path is a directory
+ * but is empty then the initialization will fail. On the client side this
+ * can be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562
+ * For the server side we wont allow this to fail however, as we require
+ * the certificate and key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites, set
+ * the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers, *c, *b;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok_r(ciphers, sep, &b); c; c = strtok_r(NULL, sep, &b))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */);
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */);
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that
+ * we can selectively override the ones we want while still ensuring that
+ * we have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch(v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value zero,
+ * so we use this to signal the caller that the highest useful version
+ * should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+ /*
+ * No guard is required here as there are no versions of NSS without
+ * support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 031ca0327f..ac11db7575 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4233,7 +4233,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4291,6 +4295,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4319,8 +4335,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..38249314d5
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..fc2f16d847 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ //void *ssl;
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c199cd46d2..dba1443758 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -889,6 +889,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8f3ec6bde1..2a5b59b998 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..863c58f20b
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,959 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+# if BITS_PER_BYTE != pg_BITS_PER_BYTE
+# error "incompatible byte widths between NSPR and PostgreSQL"
+# endif
+#else
+# define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc*) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+static NSSInitContext *nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_type_nss PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled library
+ * would cause NSS to be stopped for libpq as well. The fix has been to
+ * introduce NSS_InitContext which returns a context handle to pass to
+ * NSS_ShutdownContext. NSS_InitContext was introduced in NSS 3.12, but
+ * the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal values
+ * in NSS, but the usage is not documented at all. When using NSS_Init
+ * initializations, the values are instead set via PK11_Configure calls so
+ * the PK11_Configure documentation can be used to glean some details on
+ * these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ //status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust");
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which copes
+ * with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch(status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access information
+ * on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch(channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_type_nss
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in retrying
+ * as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..1fc7e43cfd 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_type_nss)(PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_type_nss PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_type_nss 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 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..b54e4f91f8 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,27 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/both-cas-1.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
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +59,9 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +89,11 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +107,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +186,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +214,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -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
@@ -112,6 +239,13 @@ ssl/client-encrypted-der.key: ssl/client.key
ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
cat $^ > $@
+ssl/nss/both-cas-1.crt.db: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ 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"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+
# The same, but the certs are in different order
ssl/both-cas-2.crt: ssl/root_ca.crt ssl/server_ca.crt ssl/client_ca.crt
cat $^ > $@
@@ -127,19 +261,41 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +307,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..ece4a5b295 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,27 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1 if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -149,44 +125,51 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=require");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt 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", 1 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca cert_database=ssl/nss/server_ca.crt.db",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
# Test with cert root file that contains two certificates. The client should
@@ -195,36 +178,42 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
"cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+# How about import the both-file into a database?
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases ", 1 if ($nss);
+
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"CRL belonging to a different CA");
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +226,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +251,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +264,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +274,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +288,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +301,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +317,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +329,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +354,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +379,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +395,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in unencrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
"certificate authorization succeeds with correct client cert in DER format"
);
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
# correct client cert in encrypted DER
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
"certificate authorization succeeds with correct client cert in encrypted DER format"
);
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,7 +471,7 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
@@ -483,6 +483,7 @@ command_like(
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +496,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +514,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +540,15 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ qr/connection requires a valid client certificate|SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
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..d13b3922c7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,65 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'NSS'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='ssl/$certfile.crt'\n" .
+ "ssl_crl_file=''\n" .
+ "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..3b089d2b6d
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = {
+ _library => 'OpenSSL'
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf = "ssl_ca_file='$cafile.crt'\n" .
+ "ssl_cert_file='$certfile.crt'\n" .
+ "ssl_key_file='$keyfile.key'\n" .
+ "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 81%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..8860dda8e3 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,9 +184,22 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +207,17 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
$node->restart;
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index a13ca6e02e..112e30c803 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -476,6 +476,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -529,6 +530,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
--
2.21.1 (Apple Git-122.3)
On 15 Jul 2020, at 20:35, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).I'll be happy to help, particularly with Windows support and with some
of the callback stuff I've had a hand in.
That would be fantastic, thanks! The password callback handling is still a
TODO so feel free to take a stab at that since you have a lot of context on
there.
For Windows, I've include USE_NSS in Solution.pm as Thomas pointed out in this
thread, but that was done blind as I've done no testing on Windows yet.
cheers ./daniel
On 16 Jul 2020, at 00:16, Daniel Gustafsson <daniel@yesql.se> wrote:
On 12 Jul 2020, at 00:03, Daniel Gustafsson <daniel@yesql.se> wrote:
Thanks for taking a look at the patch, I'll fix up the reported issues Monday
at the latest.A bit of life intervened, but attached is a new version of the patch which
should work for OpenSSL builds, and have the other issues addressed as well. I
took the opportunity to clean up the NSS tests to be more like the OpenSSL ones
to lessen the impact on the TAP testcases. On my Debian box, using the
standard NSS and NSPR packages, I get 6 failures which are essentially all
around CRL handling. I'm going to circle back and look at what is missing there.
Taking a look at this, the issue was that I had fat-fingered the Makefile rules
for generating the NSS databases. This is admittedly very messy at this point,
partly due to trying to mimick OpenSSL filepaths/names to minimize the impact
on tests and to keep OpenSSL/NSS tests as "optically" equivalent as I could.
With this, I have one failing test ("intermediate client certificate is
provided by client") which I've left failing since I believe the case should be
supported by NSS. The issue is most likely that I havent figured out the right
certinfo incantation to make it so (Mozilla hasn't strained themselves when
writing documentation for this toolchain, or any part of NSS for that matter).
The failing test when running with OpenSSL also remains, the issue is that the
very first test for incorrect key passphrase fails, even though the server is
behaving exactly as it should. Something in the test suite hackery breaks for
that test but I've been unable to pin down what it is, any help on would be
greatly appreciated.
This version adds support for sslinfo on NSS for most the functions. In the
process I realized that sslinfo never got the memo about SSL support being
abstracted behind an API, so I went and did that as well. This part of the
patch should perhaps be broken out into a separate patch/thread in case it's
deemed interesting regardless of the evetual conclusion on this patch. Doing
this removed a bit of duplication with the backend code, and some errorhandling
moved to be-secure-openssl.c (originally added in d94c36a45ab45). As the
original commit message states, they're mostly code hygiene with belts and
suspenders, but if we deemed them valuable enough for a contrib module ISTM
they should go into the backend as well. Adding a testcase for sslinfo is a
TODO.
Support pg_strong_random, sha2 and pgcrypto has been started, but it's less
trivial as NSS/NSPR requires a lot more initialization and state than OpenSSL,
so it needs a bit more thought.
I've also done a rebase over todays HEAD, a pgindent pass and some cleanup here
and there.
cheers ./daniel
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend-v5.patchapplication/octet-stream; name=0001-WIP-Support-libnss-for-as-TLS-backend-v5.patch; x-unix-mode=0644Download
From 06eeeaffdf934174acdbbfa7a536fb5d68648363 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 20 Jul 2020 15:27:29 +0200
Subject: [PATCH] WIP: Support libnss for as TLS backend v5
---
configure | 211 ++++
configure.in | 30 +
contrib/Makefile | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1032 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 962 +++++++++++++++
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++
src/test/ssl/t/001_ssltests.pl | 278 ++---
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 64 +-
src/tools/msvc/Solution.pm | 5 +
30 files changed, 3109 insertions(+), 243 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (81%)
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index e91e49a579..35abe629bf 100644
--- a/configure.in
+++ b/configure.in
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2831fb16ea
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c,
+ *b;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok_r(ciphers, sep, &b); c; c = strtok_r(NULL, sep, &b))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6f603cbbe8..78e1ed5b8b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4250,7 +4250,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4308,6 +4312,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4336,8 +4352,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 73aa618166..8044e1e17f 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 45b6a45789..3ead0ac546 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..23969ade8e
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,962 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..c27313f86d 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,18 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 81%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..f6b9711bcd 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,9 +184,22 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +207,17 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
$node->restart;
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 023da62382..8856750856 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
--
2.21.1 (Apple Git-122.3)
On 7/15/20 6:18 PM, Daniel Gustafsson wrote:
On 15 Jul 2020, at 20:35, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).I'll be happy to help, particularly with Windows support and with some
of the callback stuff I've had a hand in.That would be fantastic, thanks! The password callback handling is still a
TODO so feel free to take a stab at that since you have a lot of context on
there.For Windows, I've include USE_NSS in Solution.pm as Thomas pointed out in this
thread, but that was done blind as I've done no testing on Windows yet.
OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).
In addition to some work that was missing in src/tools/msvc, I had to
make a few adjustments, including:
* strtok_r() isn't available on Windows. We don't use it elsewhere in
the postgres code, and it seemed unnecessary to have reentrant calls
here, so I just replaced it with equivalent strtok() calls.
* We were missing an NSS implementation of
pgtls_verify_peer_name_matches_certificate_guts(). I supplied a
dummy that's enough to get it building cleanly, but that needs to be
filled in properly.
There is still plenty of work to go, but this seemed a sufficient
milestone to report progress on.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend-v6.patchtext/x-patch; charset=UTF-8; name=0001-WIP-Support-libnss-for-as-TLS-backend-v6.patchDownload
From 4da394fb8cb7d65f3bfe8fa244e347a1323d14e5 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
Date: Fri, 31 Jul 2020 19:40:26 +0000
Subject: [PATCH] WIP Support libnss as TLS backend v6
---
configure | 211 +++++
configure.ac | 30 +
contrib/Makefile | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++--
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1032 +++++++++++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 975 +++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++++
src/test/ssl/t/001_ssltests.pl | 278 +++----
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 +++
src/test/ssl/t/SSLServer.pm | 64 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
34 files changed, 3168 insertions(+), 250 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..00ae054920
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..6401949136
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,975 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..c27313f86d 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ switch_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);
+}
+switch_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');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,18 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
index 1e392b8fbf..f6b9711bcd 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSLServer.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,9 +184,22 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +207,17 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
$node->restart;
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.27.0.windows.1
On 7/31/20 4:44 PM, Andrew Dunstan wrote:
On 7/15/20 6:18 PM, Daniel Gustafsson wrote:
On 15 Jul 2020, at 20:35, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).I'll be happy to help, particularly with Windows support and with some
of the callback stuff I've had a hand in.That would be fantastic, thanks! The password callback handling is still a
TODO so feel free to take a stab at that since you have a lot of context on
there.For Windows, I've include USE_NSS in Solution.pm as Thomas pointed out in this
thread, but that was done blind as I've done no testing on Windows yet.OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).In addition to some work that was missing in src/tools/msvc, I had to
make a few adjustments, including:* strtok_r() isn't available on Windows. We don't use it elsewhere in
the postgres code, and it seemed unnecessary to have reentrant calls
here, so I just replaced it with equivalent strtok() calls.
* We were missing an NSS implementation of
pgtls_verify_peer_name_matches_certificate_guts(). I supplied a
dummy that's enough to get it building cleanly, but that needs to be
filled in properly.There is still plenty of work to go, but this seemed a sufficient
milestone to report progress on.
OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend-v7.patchtext/x-patch; charset=UTF-8; name=0001-WIP-Support-libnss-for-as-TLS-backend-v7.patchDownload
From fd8f8b3a31018d7f39662d92d454a606e4ef0005 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Mon, 3 Aug 2020 12:32:10 -0400
Subject: [PATCH] WIP Support libnss for as TLS backend v7
---
configure | 211 ++++
configure.ac | 30 +
contrib/Makefile | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1032 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 975 ++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-encrypted-pem.pfx | Bin 0 -> 3149 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-revoked.pfx | Bin 0 -> 3149 bytes
src/test/ssl/ssl/nss/client.crl | Bin 0 -> 418 bytes
...ient.crt__client-encrypted-pem.key.db.pass | 1 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../nss/client.crt__client.key.db/cert9.db | Bin 0 -> 36864 bytes
.../ssl/nss/client.crt__client.key.db/key4.db | Bin 0 -> 45056 bytes
.../nss/client.crt__client.key.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client.pfx | Bin 0 -> 3149 bytes
.../ssl/ssl/nss/client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/client_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root+client.crl | Bin 0 -> 393 bytes
.../ssl/nss/root+client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+client_ca.crt.db/pkcs11.txt | 5 +
.../ssl/nss/root+server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+server_ca.crt.db/pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../root+server_ca.crt__server.crl.db/key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root.crl | Bin 0 -> 393 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-cn-and-alt-names.pfx | Bin 0 -> 3349 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-cn-only.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-multiple-alt-names.pfx | Bin 0 -> 3325 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-no-names.pfx | Bin 0 -> 3109 bytes
src/test/ssl/ssl/nss/server-password.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-revoked.pfx | Bin 0 -> 3181 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-single-alt-name.pfx | Bin 0 -> 3213 bytes
src/test/ssl/ssl/nss/server.crl | Bin 0 -> 418 bytes
.../ssl/ssl/nss/server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/server_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/t/001_ssltests.pl | 289 ++---
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
101 files changed, 3276 insertions(+), 257 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-encrypted-pem.pfx
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/client.crl
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.pfx
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+client.crl
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root.crl
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.pfx
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-no-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-password.pfx
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.pfx
create mode 100644 src/test/ssl/ssl/nss/server.crl
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550b92..961cb56358 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..00ae054920
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..6401949136
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,975 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2e541cdfc9c6da2ce489ad74bc2f32e25028361d
GIT binary patch
literal 36864
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
T<IT@{@aaNdObzt5rU3sJIhEY#
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a6cb5d9248a87e322d39172bd495eb428b2a035
GIT binary patch
literal 45056
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
KtL=5`OaB6Y_1OIY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..dfffe92b90
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client+client_ca.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-encrypted-pem.pfx b/src/test/ssl/ssl/nss/client-encrypted-pem.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..940af9f2967aeb1d3be1c41db6cb2d1e21c757ba
GIT binary patch
literal 3149
zcmY+Fc{CJ^0>x*{7`rGUg9(LXnV~_l?~}2GPnMYyYV0XODhy-Iz7vhIWKH(nAWI?3
zkewmPzVA!(`p$XpeeeBo&pqefbMBw_2gfs`Xn-I%p7|VvK`dA|_<#jS2PEQ|0bo4S
zDICvq2**P{{!>AScnI-N^o9lq_+yFxG(b2ngz?`C%s>d76-*B^sg)fCyam(H&;$JO
z5FBk3u(<#@Tp{AY)Ix+PJuFYL6FZG=V1Jq!^lg8gG73&qCcPD5h)?g`9ab=dF?M%L
zP6$Be?j#~zI{XO7GGQ<I-JThgS5}L6eVlSj^LYy)`iJAj`7B~N*_RsG$)ny&!XlrO
z-Lxe1PaJ^^Tvu}jOQ_Prk)8M1a7&ppz8Zv~n>i@Xi3#;-iaYi+p_To2Tkj1r&DbqX
zPH}h`b+{|n!5l{BRe>Is<(6jwO^QsZw&;aNGhFDdt<qFzIk#EU8|mTEU2UZr?hM=F
z(EGON)gsC`>&rRyQg7~`GpA)TY0~u)JMYn*E%PEP=;qt&MHw^X&wyZ4eA;+_U!N^Y
z3JFQU#G%$qTmV|hoVatG-}yMN_r@*n6v%AYeqm(-l}q$Hen;|u09)UNzLk{)2~=Ab
zMvu%$$uA?wnR=Q$541Aw2GR&N3{_mV3?VoqR7E=4VphrShBpqWd(pSw=)2J@Wt{@`
z!|#ZTwro-&HvWPQg6NL3vgH78r;mQ`rIHyfwi!fi#u;8mjksNT<K}dVxtE@^iK$t7
zCNnqd*f^k>t=2#W_wT-syEW70%YoOnIVG%2d>461O7l1I$Y^u-WE+YwY{9NkYZ3eJ
z)}R&G#KR%c?AXos<DD;96%0?GZ#8k|c=cS0fy1{BcJ`te=Q9>S#@6{Z+bHzRcH&sh
zylPhV%Z7bFw<xa|f3!=T;(+8$XO&s(?!-b%HQ0nU&76fAWN$krG;KUbc~X9@Jtjv7
zJaVP8q^IV0sCPqHo`cT$CxRB+I7N~6<$jAn{a0PV*Vu(q^ZVYRiRHbsc`}Y9$&{Mb
zX5kY15by<q;XLDm!T7n%hcxfmeZu5hP&2fqFPJmzKC>skTCGry#d2R0w0Lz+)f<Ay
z(A(M!nR=2t!pGs*8D2n#ebyY`m+3vkBCUEqLc<1jWU7Ad@%Bi=spx<XWsJl;(@G;o
ziXAcZ9r9S>0wwV*vd?oUM{(uX?V<g+{g1C5-0kihyR>?VmRlNOT8Am+I`8Bs-@1`9
z4}wL!CL}U~87^RKUVl?dyP?Jy5gT$-h04lbhO1;u{R@3d_{xw&6fVUf$y)kafD#tK
zcl=;hbZOuSW5W%7s_MqmvzqOPpy}<1Hd}MfAqf;}>F;57glnBY;g^hl`PJ{rj0M9b
zRY*06*&#dHVph*z8)d_+thd@KbMM@ukUc6(rQOdWpaeu%XY#=m@ZcR?+d3cp+Yt>O
zX3b+=g@CvguMYHayevE*M#}m%F4}*5hxhuu{D>9YOzceAnNwD@mT9)n{xb_B(jI%h
zKGT~!Mx-?0vl^uQp%k}y+R<@{t&!~&-So$`8CGbL9q3cW&x_wJljEGf4g4(eyFA%I
zpK{&|Vlde5FQ~nC66LNp>SU=U03y=gYYi;tEVENfjaJ`gegn=eO%hnw`W(tICkFXK
zv1Z8ie3BCRLg&2B#KZ>b6G`3yBT=w$-SKXlEEM~yAnpfZ|42@#VBs9U>KJ2S{h-Tn
zfZI_Q)XyT1OjvZ@4Zok6QLiAG_^YMvDOZY}sL&Fg35NFf2rvaFX?}M#?8}fVWg+|X
z)iw16AQ`LF0#{A;*fwGLB3HppREf|A=oW6}P8Ll<i(8krPG3g_NE9enD=-8!Dm8RU
zf+}%dhWkHPCem{ljch17aH1>O>2gWi$p;Pc{_}K+J6`p~x`?lZ4~2euwYm?AI@K8t
zV30_S8HsaW-%Uc#Ns3}LDvyNkb~$-kTxnT1Th_HQ@=6-c!YGEm@-{~+PjjTxJ=+_v
z`OsOQt+-gVbJtvhN0uN*vvJen$NohnN^u3fJDb$tNIna_ag5lMKz`_l&G5dJ*Hazj
zDRE`v`g(p8(J}6P$KcZ2!udkG%%1$!xF)e@eG&Y%`6jLw8&U|u>}2aUe{(|RR_k*Y
zeZT~i{<W+;t}GOd&lXEDY=4YDNnc^ruKPH3HJ7NH*M>7N0~Wq1x7x(A8b+O7iDRRH
zAGpT(1IT0Wd+IS~8X>5*8#Pv5LH!J%a7cg8feygeHiXs}8x&MM{K1OX)xv0errLI&
zC$&@2)x3^Z{#c@9^vTA$?2Iub$m4bU`s@j5#6B9ls{3JjU9ljGAE+<=zS+cJV@GKG
zNp$*GW8^|0UQye%v4TqB9vCM1$|$uyM!@m3YX1W!BA!+njHi|S6N~+kFofwpm|_Id
z5b@x3I3B$GfAj+VO)oio6CF%6`orJ!0^`A5x%(LtgF5rIa=O=DPaqQ=bwM(|dZ;;<
zrV*ohWVo~T9!9TTt*U#OKRdKUHBZch{^}1;6h^C*x|C5GpJSCC!oNDJ<-|o<!^3f3
zs@WQg7ZYpGx?wgsk%rEjA-f(tK`)+T<&7sJ0o4e5gwf7~DX1E__gyR2C$W7lR^`hh
z?ZMs4wY8F;l#e$g&*=6I6*Uy39{t`8DB-wM-C(sZ-GpZGZOXw>C4zCVG&&6~!x#Ko
zB`5>kJcdd#sbI29jb}R&p-z`1T-Dlsi6hmZ`iWez0v3~UZWHXCrL`E(<XdjjT<usq
zx3I}M!QfWZ{}rc?<@{!xT`g<U!9KFLY|%WD%*;G%zG&&+Av-L2A@GxWn;QCafsW`|
zJTkayLka2Lt7va=M}U6mAVMOsXetk3P8BX36-lsn!Vf@txj<w<M-)-|0lmp!F-z}7
z<^}CfRneflyN8^gn_epgnol2OlSwP05j%vyrv<zU;Xt#Kd@tJo@sI-#?Qg83vtsr)
z2NjNs6czO9T?8EON2%grsP&9r3<)uWMP%aR6XTT?@rSlXCS~5XIz1QCeA3h9Cm#-)
zC`Va_J5aBh$9lK_8Xwh&6q-*4L|!m%ua)s}x##X<-3N!Wh^MYS(C$!=D3g$bxNFkx
zw3^_(_=R%8g1>Z-B01v{sH(M`uK3LdWo}ruEhhCG!OTf2_w3M*N^Z*SbdAVNs5)9$
z4<dy<B`0|7^GU5r{0KT+o{Y(Hm%deqkY-u>Dd&2AVIc5M(A@FU<N=#YlUzz=H=hQ(
zSPvvrbgQ`EHl!^=!+QP8GNCc^nfi>B--}4L>Wc>JGcIegma%m(u!cQ}Pt|5d@+)qy
zs=Df)`IjEBetOWh)2G;P3?SYLDOWo^Xc)J1oP2Xa^olLfiiiw5KWW`2G&Fto{P<wJ
z0TuQ^_2O3J$En=@)cyM^*58J|JnzSp%tp|Tx-hGk8a^nsdt#YhaASE1$#(nQ)IP}K
z6IwiO@In}(@mXwZ(^SDMv*p9U;F@jU-g)_wY1CnJC$A0vyIYL2J-P@8`Fe|Z+=k?q
zc38LDX`hk?31Ep1V^ewbF7160&mkS5+l+!<k0E4AHJY}7K9@3kYBZ|K-UKfdE0U?!
zgd_TP7J4UpG1n>m=ZI6gtNz2KS2Du{J)>0_#`bWu!WdmU#!J>9|Cf51%5vHLg0e?T
zE=dA@KcFU*hK56|`H!D={s0MPb1?})!_<ijXT{j`X`k{eWGVGc`-s{2Ev<3OlDjnC
z;$K{=mzOd=8u7!5I6@{JH7`3AAR~s%*FqOC<wchW?*Sj^`25-$nI3tQ!P82fV2Q}b
zPGSl_2!^YF;c8b+<j2X)rBSLhc$y1?vGNMnc04y@2q-x1F!3WJwLC6!yi7Tl)!<;L
zZ?xW{kah(<z+(-T9t!LD9i@pOd&3{p2r%BeOg%&i|1#$l@w7XbxXxM~df=X|VRL^h
zA>bv~_En%2duaa`9u(|;39(?(IR!VE&{ub2S(Ddv))SIdfpYM+au|{?uYX=}<ZxE~
zHa7nRXMxR0Ep&2gsX=`EnF>8XS`q^9>bLP08C+hY)Wny6UCdO5dUY5lrthC7bepy7
zzd~Gv3;p*U88{Ia!5onBb<cKsyy&TpN@0tS^hS>jyCNI`7l1QBXs@$^Xn1G=U=CPI
jn*)YVu~usO@<YwNl!$18tTvcl6QPmBn7Ziq_e=f@w`J`P
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..78bd1528d61535531d652ea1fae1e1a0b749d696
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5eff0aae8ca3c4fa615a1ae29580f9cc407581b9
GIT binary patch
literal 45056
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
VuUpY+#=ZPFXt>|`T2$G?e*uT81eX8+
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..67ce598fd3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client-revoked.crt__client-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-revoked.pfx b/src/test/ssl/ssl/nss/client-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..2ab711880535eb3cec99c033365797487765f042
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crl b/src/test/ssl/ssl/nss/client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..ec061fd17bd11aad4eea2cb4980cdd9620be5f9e
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sY=etOw9vQsYNBl;=G0yhK5GwhQ=larlwKi
zye4J_CWa<Zu7QYw5ECO4nj$o3FgGzWGQ=5uWWFNvib1cOm7QIfWA#LK*3-R$b#Zr+
zQzt9Of9Pf2QgXg#^7|Q#`?^;~od5q&E_3mbwX*KnbDH;uHx}8yc;&n*Cg`u>*0_IV
z2Y54&+qGrPUC4dt$>ZO*)%q{4x14vAo8!GyW3<{9)zs`N>D*O<h4!Wm&zYD%)*XAW
z?8h?G^mEIM=O3^Yk(m1JZpp$KRu`9gue$prUuR!%Rb1VVhy91s9M8Qw^)xx^n<TUM
zv3Uho7B_OK8=h5cdh)~l<(Z{@Hw(mAHc$Slv&lrgXD46g|EyT1`^j4q@6F3L%d)b)
nrK%&VUs_%AV!{6+)9&7a`}PORtmIRl{k+g!;Q6Fz-w|N|QYfe5
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
new file mode 100644
index 0000000000..3ac5759692
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
@@ -0,0 +1 @@
+dUmmyP^#+
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a50e21d0e379f2b24547cf52127b06329c6bd67
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..3884ab59f5568f08e5c65bd753fe7a24c4bb2ae7
GIT binary patch
literal 45056
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Ðea6+}HexqpgK%){B^(j^1Y3gf
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
OLx$%;!>EIuY5xMt<fVxK
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
new file mode 100644
index 0000000000..190f880c0a
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client-encrypted-pem.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..96ef52b347114a4403c97567215841487f8d9d29
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..bd26c68db1e1d12b48ae7b38be0a2df3661cc094
GIT binary patch
literal 45056
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
MpZrikc6yKh3rY97u>b%7
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..3f0a9ff5b5
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.pfx b/src/test/ssl/ssl/nss/client.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..ba13f0cafff3a3d64ab5c1b26e98033614e71fcb
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..e0a8a94d56e8c8fae91d40d6c67ba5778f0e122a
GIT binary patch
literal 28672
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
HHvs$=4>)Rp
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5793b879513b18bb5467717532e575556422341a
GIT binary patch
literal 36864
zcmeI5drVVT9LIb6raVfft|}szDo&K)xwp5aAahheAuv>g0fP*0p$f`F+lt_ZQjBP_
zsdFkujjxQkOvR}9*rp6{n3*_rb0NB^G1D-!5uY(SlcBrkwB5xx3(5Z3$gfFHfA{x0
z=X<{Ab8au(lF-?+(o3B-dTyn?oOjX&Nw9>XBndPvkw|31(@%JMA4(yRdk=(Hs!#l~
zP$r3<d@oS@heWN3m-ye&e&heSKkpx=xuS{J?0wlF$O8c&00e*l5C8%|00{gy36!Z+
zy3kO{HPOji%WUN~C!fSSdGD(_F*Dhcl}u+@rlu#;-Yh-7w3wcmnfjI`GmlP7&ZDz4
zQfFl+(=#&Y#2FdW(o++&=*;Aq>6XOgC|dZ<w48Cju#uD0N?l?oB`K{awpBY8l?lZ{
zezCKX92a`^3mI?ZH&v<B>B6ae(xGSpUs_z@aLlxqR`E`o%404yBPltTCPi#P>_Cs_
z8N@~=D2PT5(J14swlcAc@;MDsd_ltyIZdwAg@jXuZrh8k^KC^=QA6>ZhQnQcL4`p~
zWTH%|8{u6|sl&0@W*24T&&jx}FQ}NA$W@t?N*5kZt*s<mCJKm=)Fb8IHZebtjk8Rh
zk(m`m^GJ9K`^K}v(|}`=z!?Uo%=plZ56$EdcQJFgG2+IAn;6`faTALh9yeB@i6v6V
z-4aEFH5lZc2dgj$%P<J*FbE4V2rDrNOECy*F$jw>s0g<ndv1S3nRUlRnI+0BQD%uU
zOO#on%o1gmC>zLF0~u=|V+~}ifs8egs)<xhq-r8nle<ckO+-0{^dyuRf{7*TWf30B
zlJ&C0vw`FdByS*jj$}BJ;XD~U3y1J593jIIG91z8h(1U5!AQm$$yg&9Ya~<!O@vqm
zPwok^do#`=(z(cHG;jtZ?%)Y=2TzD+_k`GjC&a7ugm}rG5EJ!;7^^!Zd_18zjF9qV
z#bKsC!|;~4JH&_{V!R$qqF*8O`V~TNUm@(%*E_M0_4*Y;@5DmromdFH6AK|e)vyO4
z#OIqQ#3!96EEYqX7|#7WBqxo#uT`{T#deB~JSwlstub<@LNc3~9X!G(+%<9mBG
z{;3j(n_%3)0|)>CAOHk_01yBIKmZ5;0U!VbfB+B}tOPW8VQ~LHShp9}1q6Tq5C8%|
z00;m9AOHk_01yBIK!6Z{`+qnQfB+Bx0zd!=00AHX1b_e#00KY&2n>D#aQ{E}_ZU_P
z1b_e#00KY&2mk>f00e*l5C8%|0Pg?c8~_4900;m9AOHk_01yBIKmZ5;0U$8=3BdjT
z;NN3dArJrpKmZ5;0U!VbfB+Bx0zd!=00I2}|J9l<i8eQ=B`7GcG@vse%zvqnfCmr&
z0zd!=00AHX1b_e#_`eWv>D3BL{VNhj>Ab=so84K+8f>i9%&@`{`7*9p<f=mjO1}vW
zDGiSUDMb*Pfh^)-0OI6yNmJX}<|Fqjp6v>_Hga#0am>12uI2Hvoc!MUht2b9<D?WN
zrD&v4s@*D5zrL}Yt}40VQb)AtS9X*~jZNRZ@5iy<r5!(CIX`Lp%!sW`LHk{Lm2Z>g
z0hp}2(mG_=7~7-pqq~<seq;2iQ7b-M^K{LnMH5UL0zUr!)!F@-+<(7uo1tMIvwh8_
zs+7#?*8Cl5s>opJnUR}gcm14uGs&e_`ZQ@Z4#4EqP2<j0tLrRJM%`_GCwh3su}>q*
zPj2s=wH7I^AFS+A_h)j!(avj^Gq0{hKjn6p?T>yIdOJQ$yJf-pB5O-esKm6>rC0bg
zX|xW&<ehI{`!aiDec<X(hN`ZFwM0vwD9SW#L*q*FPlcUaeep<tCWo3*)}0Qo*i%y*
zUvRj2G~E?wzh2j29--{H@kqC7GwssLeVVj#128%Ic8>b>-pg@Y_SJT``2}|6Hr9>o
zuACSmtGge!>S@`A{!IR`nhAO{^pt(_q#<X{XB>EM;__kRmJi$bB+8bvf@Zg*xb!lg
zCWWt@>;H{&JYhss^qH2nO;g6rkJwO?{>8EM-&+ryWlmpc>}Xn8^q@bJ2kk}rZ9yAX
z*3MoSUyx@^YkIeR>ZNo1#;Ww#hFjZCl(_U#pC(Oc04Cqw&N!F-mQ~gDbpPR!A+0rs
zPER@T^`YbMkN72FX__W`dVeP4e%(5M$L#{uPVHn#{fUYv9rk;{?Mu!EU(v?b#3bJ6
zc12K%P_BOHLf+w6QfV(<Ds8#iG@RKYiH<qFa_pkpQ^s7(MlKgB7q2>jXwC~4A#1OR
zzVPRv#?D0vuBzbLlwR({`1WolqBF!_xBi3nYyV&~MCmuDA%(KZ<kSRtP>=1wN4FlD
zKU^r^yJvWlJh*&XLDS8z;%@9R-3?RKPK?HX|5s~6B-)4CIBkgbn6_HmrQNK}`@dZ3
z!6y&^0zd!=00AHX1b_e#00KY&2mpb9Nno_hLcRPGM77VdS>>y2H7b3TjaG%PvXztj
UDjRv3ud)eA{biwk-p?ie0)~o{+yDRo
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..212e72edeb
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+client.crl b/src/test/ssl/ssl/nss/root+client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..54a75ba497fed1145ef8b0987417e5de6449496a
GIT binary patch
literal 28672
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
N0Ryi@{@4QmzXywqjwJv9
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..55494e66c0b0aae452fa1e58ff6daa0ee72341f1
GIT binary patch
literal 36864
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
Yl|@byuFMPSaAhlsh*2j+hd!713q1s>zyJUM
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..bdbedc732b
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..faf4480a51f08ed862464fc42753d1002c886912
GIT binary patch
literal 28672
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
H4*>if6nm11
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..391fefa234b49726959a94205089a04167c44fb7
GIT binary patch
literal 36864
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
YD|-ZWys}AD#VebUGD(%082en}FC><sZvX%Q
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..5d04ea23ce
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1bef7b32e1a48253dcded78d36eb0a22062511fb
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..633f26005c9734b998e0ec8ec699cc1c0164be48
GIT binary patch
literal 36864
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
YvV)U{D;s!OxUvyRqhv|y(B~3=1DlPWL;wH)
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..9e34f7d8ea
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__root+server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6dcb06f709163abaa8ff8779475330009cb10925
GIT binary patch
literal 28672
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
I=mmiP1_=>&y8r+H
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..572042e848fb394849dfc9a4aae5f811603ca074
GIT binary patch
literal 36864
zcmeI5du&rx9LIatuIpo62M!9(y1Pyu#mUZnwH-pFj1CH;doW}J+;+QlP}!JU%OgYx
zg^5IsOaqRoXh2ax!c!g+U?egEA|eQziaa#n3lxL+Kt;XhwDm%eZi)Zs@@vx5-~Ij0
z`JV6joZGwIE?LJH=J|s@y1K5RRt(adB3VIEis3Y^P$-nrQ!72A549Aiq6g9|bwhl+
zP^lPv|Mv#tFA9xrs6zjp@g4nIy{J#qoze}}t-IYI$O8c&00e*l5C8%|00{gy3DhJc
zn9OD>bZ=1f*7$0DL2;xQ6r-=2oFbRA*hLpRN94KaXqF!6uc8Zz+~b@@rSw==DLujC
zE}Y<^^F4G<z9%=&ol{H~xeD@}Ij&4v`pvYK3BRz`EREWfW2O}T8CAYHf%+P$ST4>C
z){*1#sD3#UjkF`wYLh9QnoK%WP8a=EGXjBv2LCKE=u3#0b9+X*N@!BV7UT}}K#@VL
zb$C3{m`F5gM6a(#?xMa)gA{Mk;H<eSwJ9~7Di7P9>3!H&8I(2RZ_)^a%eSa7h_U7<
z)uz7D)%XK}nZ5>DMs<@+xO$6<gRwqdL?xKg)2XNH$d<_haugSl3U8a7@5;tGN8}e3
zXVM~)p3=UFtn}n?OcFT5;FJR&I`E-`9O5nx9ybDRY`C%G#(|q`+=#gGN=-JALgAJu
zBCNrn@H|+BL0E=CScgGah(TD1L0F1GSc^eej6s#Sjo1tOBg$+zCdw>PW{EOOlv$$8
z5@nVsvqYIAV>vRGBV#!-mLp?rq-rBo8>!kz)fTQ2WgAhplb(bULol&qy)43GS+ZW1
zc;-l+BYBSGd6MBthL2?MEIh)q@PrIc$nZp;C;B|u2Z4+g$XJ1l6$n*H6CswtlSe}A
z-hs1-bguLX9M1{3LnOo<A|akV5@HLH5U(~8;w48yOf(W=tl^OGiG=bnLMoD#huLlz
zhPNc#!7h8SM?KhNzfu_WD}~X%Qg}n(=)_Vs>Q@S*6H8%qVkwMHEQR<~!ycp%pKp;6
zpLCJ1N)CN;Sp9cMPMUCEuWZMQ?NkXOs`4Ua<>J()QR%n4Fv{=Xaz>8vy}btiR59Qt
z88`3%0zd!=00AHX1b_e#00KY&2mk>f00g=#0Ucf#-2Zpi?S*v#0U!VbfB+Bx0zd!=
z00AHX1b_e#AOzt4A5H`y00e*l5C8%|00;m9AOHk_01yBI-Jby5|9AgAh7|$<AOHk_
z01yBIKmZ5;0U!VbfB+DH`+qnGfB+Bx0zd!=00AHX1b_e#00KY&2y}k}aR1-^_ZU_P
z1b_e#00KY&2mk>f00e*l5C8%|0RR7gjqaqvSdz3gDaqhZ+@F}HpDQKc0R(^m5C8%|
z00;m9AOHmZF9bpsO}ukqPes5#wY<{T5G-eT!Jf_A1?h-<8CNz6EkIM$+Chs)FaFs;
z#V4VB<dhE+5wD{65B%ub#YHdOe{jq2lgC#EesWc(=|3BhrA?j0?%U{|x-gES;wT#F
z)S9qLoc6|8MfVLXHyoKU_{`Fb*4e`r)vs+!Jl1@~zp8At=hB6}XWHk5ED5nqI=WzT
zuRwj)K6UN~Xh(4S`CfOPTQ<?AyLh~1{NbOyYx;RstnbWZX8-!emfaKk4^PP3)Z_AN
zQ&ntMYVGGs-`+o1ZJlL!#t^coW192|T`>90plK%4z>?PMy?Z|Y#-HsU>I1DG@9f{_
z#1yVKTix&6ot>F%PIQ;f&v<m7dE}*hy5h^c!NSG$Yf5&^_%!Xr;wC%yK*$mw)1=_-
zg30zWWHe^Z`|YbFucr2La^cmNi-#TIdau0eA<LAem4DQCX7bP%DaU5+?12heJD$B#
zU*FNAPvh@(<}~Mkr*@s(wdc#ikVO^Kq?hl4$)J*5mDGIj!pM$m>+EemG!1!5Tj*(>
z{-nJgZGKq!`V*a*d`nz7YQ@m@-r4p|UvE2RDKWS0oi_jX=AL`r&1@XzF$@b?lrc?8
zUpd$L8>j8!X?x|R;H5c-#|$ysPc3QesC&<U^uUTc^wzPt6`_pIOfKL2;D)i|veL@V
z9B<bJvo|lfo32Tv_PYMqasKjS&s7IQmbjQEZKw+-%jzGU@Ufwmn|!vi$L^J$7dMP`
zTy?*Bezf6QV7pkg?0RP=pS;iA);FVL$G6umzn;9Yz3gn#tG(v7xIbBTXzLTdz8^4!
z`cd&_eqqWZVjwWPuAyp)v0}{Jl%wOc)&bd##nV<T+HKOJPzcq^R~-YYxaA_W?X{m$
z)~x<KO*>}SuUVz{Y??mntELG<Uzjvx*A>lyS5D9R2b-yCZN=izluxOm2C0&AHy-*r
zLwjXg!h)2Rf)-V>D)+7nKOQ^r{i%6dnhw@{bO8VTUt>&F7_S<K7*mbgjdP4Ajm^f=
z|I4Kwd;$R=00e*l5C8%|00;m9AOHk_01)_>1nyQksoQ^osEJv2B*ZFv1$C^l;Ej(}
X_VTJ&WkFQND%((;UTM}wKbQCmpCXce
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..b54121fc4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root.crl b/src/test/ssl/ssl/nss/root.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1ea3e8b64630be9ffe7463c1713556679c180e77
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..c834a04c75e060c037de9d211fe5588aa18d7198
GIT binary patch
literal 45056
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
AYXATM
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..bdd10448f3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx b/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..7a6b23bb14fe20ca32e9b1055a9c783e9ef63ca7
GIT binary patch
literal 3349
zcmY+FbyO3Kzs4DCw19-bK#5V314a(%5;kPgjS4DANq3BHX(UHC(lWY6gLL!a040=$
zmr!`G?>YD0-@Si)&-a|?Ip_KF^MRu8-6sSPLD7_p#AN&tnh}@x03-kmnsOM3rX2Va
z_dwAkUjM0xF=&#ve<B+~0KuPU@t+0&WhEy6?*&Q#F_a2OijNY`(g|wlAS5Iu2u71Y
zFYcJk*pf2)rMRj}5W9`8obk*W;qS!{WG|}IzK0FTh|7kEXfpu3NZjq7TgOCF-;h6N
z|4HFg!BCnu%T&ZY-<H3V%ELy9D;Kyy1euw?3i@G($E%S%(SCOv;j@e4Q-<|c=y&)W
z`I$embXA9N{;IF^QEV4gSu;`ep#j^$a#cj7Q|vvC2S=ryh9_y{@MYXH)bv?OJr5};
zFNT{OTdN+<4!W+}c)!uOWT~J4Szq4mKkRKPsjGVw`$CQkndc$!KC8W?yxN3nZ(!yT
zYt4~!-|~eRbJ(1}KchK$g%6)aaoU(|-gE7<Tu;wjslV;Z-hdHJ3ZZNqf(oT#iMs7J
z6O$EiIYy*Q@}u}_{k&+H&|;mXa`Tk{MaLr-Riahi9i85}+wHK&-{=E&aWJ{<@8mra
zB-@&{3I5=0Sj2QW6J31i0UHaU)gzJXz{;=^0psnSA<~t9l$@h8b-P#Wgb&L;XpIVa
zXqlas(QAC(w(HbZNuPQxeF|5dNI5fNVb4?@n(RngP-%FU<6LO)vazy`cFoAKRV%aX
z(*7^*i3AOuPc+7LHbjCh?K?jOzm36j(q{o?r$&}-aTM2e*IrkZYTihTabGv0t4_I^
zjCX?Ai$Kq??}w$?z}61y>R2Ay!z*1a*G(THH(ak_B?uaUG?=P3u;B>h(46cI_2jjG
zA^F)$iP_`(4*mlhwzjW88l85%hb)A2`%@m_qXU&SzUGN~`vh6(!DWT0&^{UM>_0Px
zmuQ{_iA_e3CPA2&X(vjjE{}uPFFNiEz;n5j_iiWVH=BK|=<R<W7OH<YTz$=XKy1=3
zrPL=hlTg{0jofuY`Af?LGi$2wb@iO^<afC}?GhwcS@Gp^`6>;`iH(1c;X3#+zdpuc
zVDutt-3M|AKeIE;hnpe9jmVkiR_aG+*-CbiTu61YUGD?@t`<X`&R~Tv{I&)rr4P@=
zwWbft8_riPU1OHWHR%u@Z?a0?mRMHV>&o+i8I&K-r0G#p`ZS+i&YL{Hx;vAO0j~?!
zN6^$SN|x^x>lSp_$uc8f_PAKLC1OI?7CvT((5dM&s1;ciizAENK#B;@i@~n*+)r>4
z&JD9Ja7v?A$geJn^SzDkyM3Ab#&!N-jl+0dG5RxqeG1YA&L`}Q;Tx2Ty0r_Y&gAjy
z!wT78oy^1ji&s~uq}okw-=f}BoKYbrNM9Dl4%zPG%_E4(cx$W8%BQalAWT*6LGhdy
zv#q|G2&C+yZtRS%nQQ%|*4JK@Qp^+{I^?IGF3i!UUEFEykxYVB&<K<MJMDH&ZQ!*)
zZR#6!2M0r*neYsVI=naBf;pkO)$Bo%-(*)v;=!{v<CIc}E7A}t!9YT6St1qt@>@5H
zXQhT3DvlB=^!1m!p@J*~@={JN!m139GNu~~IMuuUS`kNg$cn$Ul<z4&A-%vc>U@~5
z(eo1(oq0x|mJ@lZ$8;rw)mrqN_R5=R;#Y?be_RnUglyf+sTF(iune9`a7yLHRb#bW
zd7OHqrfBkGccx~JNzDKPRHriab8AD*L=9B=M$lif3QZ<{qtVD0j<7h_j~$M;{Z}-R
zKdwsc@=gZMJd{kML~$OeQ{Xb5){HUt2=pjRbM3R%&KIZp13;GVX@JaUbXzr5_ML4u
zd%HoKTAumM1DL2|mdIu5&bvra+>{)L_{<~<Wg7ikYMcx&luKO-B0cJ^|KiseLiwg)
zXDDG2hZ=1dNp;~qpte>K-T`WUK3C7(MA|Hy>FDafMcry|Sfg&ERP8I)fBIw3+TL9u
zPEvx4vBbR3$C=I7T$Ka~P61IRuHN2WI%!2fF?d>MB&ek;TRTd2p8TTq58>BgIjkFQ
z%-XMGLt`CM(2|?(&SGsTa)cve`RznzYP0lx3A?`4s@ALv7+zs+zfY`YgI2sif<~>0
zD*2KxIn*w1k+JQzl4I1j@f|#8feu&R3g_{|&6I_<)`=~jmG5nZ8ocX52s!BKI2x!(
zd@<P@yxMJHMqnhTawD8e?JB3F{9}^!`e*lv@7F6r?LdhhH!s-}X#y^7>NBWR_Rq$&
zruJDYeYrkVDaNKQjnQ04O}GZ9hqOuEAHWCz5w*92kF$y8bG@vMSG4;%?kt+rl0J9|
zpiofplNy2MdE>kC+f>T=0|Y?bQj*k`BZW@r^Uc|l%zzuc{*cv;%z1(b$q-Wf#UzQv
zaf7o!pit8DI`NH*4I3zh+KNZf#={lK%{WOaK?S}a)h$T^mEWyO3cs;bnH;Oe^baU9
z-ocHl*VsNHr_wG6jWB$PgE1{L%1{1&D^a><R<G}*%9?{PyRP~;^o%LF4$j3BT@Hk~
zUauOSB-uBzjxqHWPn`V%8vf<tnI6^*S-PQc<2v_|v553A(?1K+ZDy}Oq-!}BVUui?
zLP;x(MzI^Pd{ZAnh{^c>*oC4&O8-Z&7&J%>hz1G&iTVE=4>866=$0Hnh(QAnp=jX#
z|FbaBzZRB4ZuOn_Fi-t!VIUef{9W=f-w^4=p4hD8!NFa7ufBp#m_*d-dl^3)LXz!q
z>Ab{!&$KOzCl+JJICFr1XSGxm%h%Hwtd+2*VsadLX<%m2cuD0)*t+4-W0uH+MGt#i
zcCu^!w1&8}bCi#xAvt>5G2+gACw0;Efk(@<LG&jJY%t$oswr)V<9_#H+1d2C+=t5k
z){A@uUJ#*orM4HO7Bn&9(g=4NLPMSM?=s?a>4<rrG=yd{j1PlhVJ3s2v3f_96cZKZ
zZ1cUvFk^XZaO$YeQ|gryM|LHAQ@ET0o3OQ4N<fXdhyxR@A}->r&YMs&8yygn3f;fv
z64k%=lYy!&Nqlv5ck#z<2fxY4oNv{1)<2;u64e_fG6#G=^lan8Pp@o!NH>(X?e_h!
zdtq7aqY{ck0Z>^dKvhB3-#HZVSbIcDWg+9sjyH8&FLRe^10{C{24tM^UR%B>$(FX&
zrdN3|1U()w!}(&5S|^tub1;&~7>z;#CiOv$G+=Pn-~+M#bwI<5Ee18lh3KRH1fCsS
zO8qXTKDvEY?<~Ge3@kym3VS0pQ8V{>GJY6k<z5_y6IP=+mdk}jCbdv040-KWPa5yo
zB{=Ao883)AK53zwDMrVv>2i^~gWq|1dW)^dmoEw!&?Nd;5Gj_ALlkk1e1)!>dJ9)r
z(f+ui>A-{;Q_ffSSW`L$$hW1lUxTj<#vxA`fqbj|5f#$uqq6wT<Y3M`eBXQ1SUGhz
zWhv)*mW=75Ci&ERrsduHm3_Yqk-w#c{ZeG@1>Uv`M20w8b8voma^=YDUlYdPdtey8
zxTGgi%e*x1KBL83t9uO}_c*d|fu&m&=>Jn-oJ?3hO!!LLfGnMPQ#m|eg(#-#T!VJ`
zxlf`<fISdYBT+p_)82?py#K|f$MlQz&Mw{<^y=-|_>jC6@rkdCmy*8y2+~zT?VhS*
zKt2C8T!nRwIlE*SUcn>jzt$9(EK%M%YjbH!{chggcB1(`m>xOirp{PlJlO1)A4w=U
zMrgXFk=Wil9O~qB76k#rq_MxvGnEcmx8W?BTolhXY)hVw96oy(Y)RM<oSj?+me|Po
zbVs&~#>$E5Dyrf-hl>48<VY)g>wi4$>XA>xQYDrZP&M;vY(5tGxg%YMdL_EgH#j5I
z<7%+xq0x7ZXUFrmItSIBE^#k27q%?9!Qrfb@va|gENpqw#(_==fbJvl@WYDcUt<07
zwOXSdJBEkT;)&Lu)NSK;{maUcWQ+xS1k*&qrM)i>(vnef@ph}xLEYH2{-e>0ZgcZp
zM1-xGE)hb_j~1wX8JrBPqQ&tPh2A?hI+3pmh<rSWMy;9pVcw-CFDJ(~{sxsr-*%K6
z=94Fye3G>n)ejaF&%!A&G7g#yTFqe#LXZ1ch*HTx#V32-2nU}H*&>Y4(|`}lHf;8$
z+S!QpYg<f<Q{-q`!4&=1s>+Ea?U{&^sP44iDYKA=?H1dGuUYi_VcZT6F4KQGrWl%%
ztids0;bpBa!Dc1c*nAD?hZ!}dJQmjZn&CB0iw|DUwOfKbfjLI}H6<_CUAIS=Z{Cil
z<0O9`Sh{P<_uW^}DjTIG0g9(Ja92&|>DV<92ASYL0!h9FF>Q^8t;!7_xQTK@A^%H9
zG6vBHK>BY;N0h>j$z$+oR_^MrZ`5Bc*FvD8P!1>=F-U-lh!6}S0MgfO+S#@lJ@*~K
cB*>z3FCt4X=N*8g@0r=P=BabI|E1)A0p*KFjQ{`u
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..03d88f8838bede656dbb48a5b56f42d64ee38d43
GIT binary patch
literal 28672
zcmeI430xD$9>;fcfSfBx3kXV7q(lXFlT8S=mCGO$6uAT{HUx+oxr793y%0RW7I_|3
z3o7-#rTP%BYM;fz(@JU4YNb5Equ@nFK%cD&zL`ll<k^qyr?j8f&zpsv{O9`3%y(x0
z$!<0q780b^ssvM0)0352ftct|kR;(H5D)~>5A23u@7(yn;B`j8E7`;TC9@yl`ucq{
z>L<d$_!eQ(MwOc!FqvU80z~kJ319-4049J5U;>x`CV&b2fdsq^46Lmk$Y^y+f@*er
zqEelZqR~vuh)Yt(8~TRIy~E{#fC*#eQ38}F2%aG5$`?2*8CoP9!$%4cNFhlXr%LK7
zVep&+%ImEl7LMf^Slc;}Q&4?PM%)ZlytYe$|D1w8x3>mEGeU2!fwgVtIMf<VhAO>F
zg!i0?F0;3Wj1lGplZMs~4rG3^RvDM1>Z;ylaP&Ii-abL{o*dK(f{CF4<Gn+p1%dKt
zLBxcBkO(<!!8dq<Ur>N=xWL;dI5d2eKuLq00lNt7VrZiTn8Ls$8H~zcREDCkh>V4S
z1O`$VxWhmO0}mJ|VGsub4<tqFLZnDT4TjdW163Fr$}lw4VQ47C&`^n?p%g<yEry0-
z3>^<cefM<zL&_qZjg&=5S%j2DNLhrGMMznMltoBcjOvO}T`{UFMs>xgt`y};QLYr_
zN>Q#<my498NZB2gM3fkWNrc8LqG4ST8m|cTS&Y)fC|!)wS(L(}6jq-CTVZL~3X90F
zhzyJLS)|XRd61yG5>!`$>Piq*ph?pr1~#rYVee%yl}3f*RT42PmcRmf6Bf{$uywr&
zyP!AWX!Rx>vfhM5^(MrsGZ7!X*<B5h(x-M;llG_vr$kr4y}J+YoqdpY_ZOI*{RL)c
zSzz`k+u1Nk?d&fwI~xXOXT!kkY#5kuslh$~6RtPC2^XE-Oz1XM-R9IT6D=BD*|_fR
z#KG<)NR)I!98C)uj)Aq@fh2UUB`pb@(JPc0+Ef%*bRE|Urpx34R}0VY2{W+sxt|aw
z$Bh4Opynq46K|LRCh)&WV3CMFhi7GOZrQtgR+cPb4*m$j9OPOqHYIu&L3zEa{ie7h
ze-aHUa7bc1@iwdpUXB%&2e#%lWq!WXe(jL`>j+kvJemX>%me=yOTyb6@3|^|P9AKr
zGi1OI7dW;3Mh0_MlvowL{(5BrQ}o{Pv9Xb!gFhKL(~-#i>{N<P!R*VPX~m-9-@AHT
z%2A8?ubq*bE>C8!6<HP6&AdTIQ*-Ss_dVS*Lb&AUW$ps+qK0pc6TcDEj9e4;!Ig(j
zjO^4<K`7^Xt&A>RJG{g9>7&gVBR5pv3!2-UA)VFsD4=0h)$qYj7L99se`7(|;AunZ
z4X?e^(IDYC3GD03raTtb*G3I;Ams9cKVDa<w`6Vcd=R?%*~8qu%P+7errkZA?UR=`
zx20nFit6@x&UFVz=bQf{*VOWDr}ncu>SJ5i@7cd}Q_OMovK^Lt3(YzA6vs$#m6Xip
z@VX~0tAFo_dD$GmiTiDv71&@6b9NNr$ZgqK73Ld;W`y=)#?WsVa3gqJQ%Sf=qZOp5
zrfLPg-tYpjU}CC9J1t!m77`=~3kwpYt3X7fQKzN|w6Lfq1H7+rz}y14(4IR{K0Q0F
zcIT?gKbqIL-E~;?Zo}qxn*2^Vu3T7NJo9Q`X5O%+yu-PE+<9>2IuUuUgw^vDvaHyb
z5Ae5R=&1=X>;Q4FFg2?5S*mnJywWv3T}x+k&FF0M;U+GJ<bbi!W(GzhmijM!WJZ!E
zeB$@>q6b@>fsb&E$ej^`4`1xt)@H1fW*Jr=rF|GLdW>#H+|i8u%E>^Z5X7s}wHh#h
zp*=fJ@(FG>Nf993n3D~LeEqFiwkgxz{A|Vk5kDpg6{@@Y&l>%+IAs8Rf4bz3T)QNV
zpZVabjjMQ-lbL;8hh2UYRWjqem(SjJ#~n$cjH))Ye&{@`c+i5vi@SbytI8>j8W=6P
zn-Jn5J>ULD-ghe#ORvZeh1@kPExG8|QF#0QTxp^5jz#OeVKu4a-{gMX5LD3OfB(q8
zo4@QheJ8&(dOAn`glO6|%10$}A-z+hqO058*SV<Wm3F%WBjXq5f4%n7+1<j&{@YG1
zycAaPVL(c=e|pifXB&BLypAUU4}_{ncFt)@tB385&jR~z=bUbu<Fa+sp4f5Q_$KpS
ztFM^9_xufVa`sOVn>SraS(3A>_%)t_!v#m-*8a3PZ3;*a8o=YzT#|g&lNp$7z>FlK
zV}#NV+x^{r{S*Jy9`3ty(wX9clQI)aa+@PsRMq<}HXfK>w&k!%(>D?FkH(+5<*Xe$
z$^Cev@vz%5)rPyBpVch%-CE;$ruvwT!Z@+<q@!oSv{+Ae<w5`X;(omK-$_qgy7T$I
zn-;<L%7Sy-X0Lp3FSPmb+R4_Nmw%A9dZpdfjqL5*;mVeqOApDN+G^k5tq@!rda~&1
zkjUogKh(N<EdSo8>SK3R*_w*K*~eb3JvsH2swIzauamgRf8KL)GB-D)He~1^#f>GZ
z!o7F1jqP$0gR_qsTsd%j_2RP&Y+o(6o>HHa{fT>kdd&^f`ZZrp9q3awd}CSvb0^wk
zJ&qBP8GPre5O~b|{#6f;q&`^KpRR6CZT%)pA&IfJnt4XBb?2@5z?<?oJ-n&apX^OJ
z)l<5?X&(#`xSjv@r*kegFA5m{)q~`Bj&9y^?^xZ2MKLX3Df2EqwQE?D6*l7}!{0D2
zcPxqgsf@@o1^)Co4q5hpyg%hs$LRg3Cdp0bQ(fXyTvAh#-qSf&&QsujegNa^b(r<V
z!g#4y^<xIp_Ry=En7-2EZLRx#jB%ug>b$O9d}?xXYKlvWGFjz1bDE1rlLU*o!r-Nz
z_5c0z4f3qLCK&nA)fI2;W^41mD;$-2z4i3e6@EgSsgBF8UyqUH72X=XN;yo?W_;DA
z%xk1)*oq;G?GEj$Jg_ieTW;W!F$V`NNE$ysb5q(T+uPe7o;bQ}K&eUNK%1B-O=-Q@
zs-&WE(S?ti&Xvcmo5)GnGe2kjL??@8?XJ<6z1&`zm(zG_^2aS2RpqxA)^9(zY6+G3
zsj#u;{3DCwU*$h?j%jjjE0!08Im`=ae<B;(W;=UDS$?v~=XEEG4+*z(;sVv<i}Hw@
zX>L*1HdrvlEsq~H&+*QDwe^cBe%rlIH7q!+t*G?0FTQ!<A%9ATB4o~yrF?_5v_a7+
z&(6}ax~*yN)zl~6tlSf#IeaG>`O&`d4|E?b<`;fcHwGQ$Q{NKQPgFD2NL>LY-Y@}7
z029CjFab;e6Tk#80ZafBzyvS>OyDm-pdXh<!td(%$N=B}@btzGE;7J1OpYO!XXFU3
z;i2<?^UH+!Woo$j73v6uu3Y>D28p|f319-4049J5U;>x`CV&ZG0+;|MfC&HsJT5`<
z$MLfIlfVCNCSD)ua07T*R(*0~6vrPZ8TG}TG`jEqBMGXQQcww02^EQ#4<>*KU;>x`
zCV&ZG0+;|MfC*p%m;fe#3H(X|hFnV${QQ*<FGKM9?6jW?<v2mSEPJEA`cZ)H{Qm?&
zRf4<!>!>fN6TfnVxELmY319-4049J5U;>x`CV&ZG0+;|MfC>C51UfG==wEpjTqp9m
zryFtgIUHNA%m_T!;swv?(4GJ9Bd8lxIdzNLM}7IHbR9Q>319-4049J5U;>x`CV&ZG
o0+;|MfC*p%zd*o-%aSj8h@b8*1DN}w2d3(78nEWFMlY)SAHsM+i~s-t
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4c91baa85e018d37071a3566256c88dbf5d53f09
GIT binary patch
literal 45056
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
Ifg7Iv7nl1PP5=M^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1e4d3f5fef
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-cn-only.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..0d2d3e5bd060df6e7b5fe4f574a14ba91389dad9
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4db1426c3a88f0bd27dd3dd1593038e228c82540
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
new file mode 100644
index 0000000000..142748c1c9
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-password.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.pfx b/src/test/ssl/ssl/nss/server-cn-only.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..052a8847cb8f8feef763a7220540f540d8016f69
GIT binary patch
literal 3197
zcmY+FcQhM{-^NKJgxnI1d0Ww{y<%@_#NPDQ9yP9-MQm*~wQAL>nHoXSqLkRG_6~y9
zti8qF^VfUM?|tw4{_#EEb3V^G&!5i+frbPCNN*v~5CssJCtN-J<PTDE(i}7d0YpRi
zeq$~K8Z`TF1j<2!hJK@d04d4u*YR&cint4+{BHw<6oj}9q?kKD&Xc1s0s#ONBq3-}
z+D9^w9;pvM-gbbWelghQ@BEZfFFNiYVTonS?Ow7O@k*o4MQ~*>%}kGQ=+c8UIY;U3
zo?g4|1=U&BVNEi*;+BLs#9VfIe0`br(O#Q$@R+B(Eza2CUc__~T#U<CYB`j<^c2@4
z*=-(=p`DY64YT(FX5O4)q(^^{Ht9|EyhQ<;dfl4)d7nsrj{V@rctm*cn1@BjF!z4M
zRUo#2HIzP%I5QV^UqPRI+FFHSQ+(;#n<{U_w2*k)aQV^A1Xb$%!$vb(i5`syacI>g
z`)@+-gAqp6YprGEC1xUB)I^m^WUq;4WbMH4fTTk!H0o6OP4QM~eG<z1C>sfmjuVak
z8MHEXkuHuWM?DF76H5Kp{7f1--*@YB%9|+}m2odQZau42mzUhszKt1%r>_3q?e88@
zi==qw=oU{LRKb#*2MU!{x_1Qp%mxGb1X5!6CAyqAaIf-G>&=HM@uDc9m$+wC<)xPV
zp}3+k>BSGgcrlUOphe|QX7Lq7$dRLsjnGYXb#0+a25!-jM@QFH;HAL}GxiHp4x9D<
z@-L$^;HK#z*$_@6d<`Sjq}ho2S__to<x(YC<gF;k{_uHfb3=n@UEVyM&R5|`=c~f?
zxb4H%Gn;0ghtB7sKl;kDrcRz=tl$#a=&f(jnJw$IWCOV(Nj;H@if*iz^RH^rRgwp!
z8Icn*r=QXyC2zo%#vXB|4w)GkQvDASd=W03A$&m!M;ph{Izm}{i)v1s##GJFA#kK~
zJjZF%7mF_A_^Xje?Ahvo7y0sKK)P_Hjm={roo%_gTTn~AM8gh9D*ST$mWoU^l|D<D
z9*f&@YNu8u@xd9%{>1g2`5BVd=*m0kG}`NUFf7Z59{EKp>;COyVZWzF3uSa#V_g^Y
zcoo?&bJ!|xlI`Folzw1<@HfqAY}z=q+v)+I{>zXFI}9C%Wv;}cyH}@bmS>Y)s&T{&
zPoxO?)1<3`juhYC=Rb=rIzR3+$;lcpaQG0C!`^+RqPUpsW^z#iZ+tt3+G|i7!P*o}
z#p7zT%j`Z0NX8+roSCc^E`~mD-C6(c%bQw#s9<FNS~9gPI((IdkU~y<=hygx{EVO5
z>LE7`+7l|c54seej4u**lr$eU8ri_<z$z|O8uw{`*?IF9^dNV!(lt^NhrCiA>XWw4
z#;z7@O|=*xJuS_WI>L7%K<n8gxzo}>+Tx0b$fcoWdTOP)U3WTS_Yp8CG72n_&c(Pr
zsYh1FJ<_)PAh4x&4h5Cx`&_$l#c>*DTgjFpLDu$~wvQDe(SO@7a?Ms%XDh%ZwlmnP
z;u0HnxSdPL+|TX*r5(isOA<RFCwgpmWxfBbkS*g6^S=Sn!#q<QP&-U>r+(aBWBqO^
z)mITFP<#wO23NX{91h0pm!Xs-nid+DO&l})I5`uJ7A0FBw*$x;)e4WKw#{5C3}yn&
z#?J{F*iTK>u7_j4ELD`O#tpm9g{5-UXU2pRc=OUxa}7VjO}C!KOr79Jaszml$R@vL
z!j%k@m-gU=86WT>{!yR#t2hXSgDN7^->rWcDzp<vb=Qr9F=1_;f4DgqVg|x5C=N`H
zeuhLgQ~ho6mBwN`k7B(MG2Dj^4>OR$?h>MqNA*;?M5@u=IW~+GB@;3tRMtEoy6fH#
zlY4{8&t6<!2$D0g=;kzBshW2LM=U2=6Bj!b#D!&CwFHiSdb&5MJ)xV#A+u%M*wg##
z$sxj;4k*`9V>?Ri8l)zlh`q`#PeB@EejZXMjA4Fl(nQNr4U805&-7&`>Qb)`RO0rn
z1<6%<<sw@D3TuC?l-P!}+wt>bXbO(ml_Hir1e2sacK(Y0zRFD5u4|gg@6*_b|Di77
zbYqut$Xry}1s(-aPaOvqoldI3<44!<G3&T_ZSyVCl8JS_xMsFzc`T*DO=GXSy>^ck
zJ(eyuZ#-Ab&iyHv1m~FIDkExG|Kc(iwrXwvV+yI<7O!k@Gi5Ju^QyEw<yfZ~zxVFU
zfaOM)ce%Ir0Ab6RDU1;7qp0Q_dsS<xJ6h{06HMUmJV2?<%<1LQYP(3rYJ5VUvd0zB
z)j0w3|Kyv5T9hze4^uV5%iYR2thcv2AQk6OQPp(7;O-EA3GGeSxD^bKJxpK^cb*F*
z^GBNc78Qk{lYF#`Rm@O#U-Sy>Ub1V;9_>lPc}%_bhF4_TUw&ECH|Xn<dK1Z#UtT$c
zO;(5T!y@Sk=w9SelCOG)a}+tK1%W10{68YgL6Zps(PRR@G0*Sg08#yq)+k8<IcVTE
z0u9{wf3~~zkL_;I{i8neQ)2(v4u}SJCy4nYb2EHi4-WFMDi+*);||Jm8pC@*sP(BS
zx@(UGZP2<LQ~Qv??_K8|#Hy#h$}}HqcC2fT4X@sdzQQ?r?2-*odEe5ATz)wU<}B8V
zk%<93uRJ^0yQUdph5o6r4Rff_nhile9ev;Seqt;H7^6-5x67R^w4j(Qz4H^N!2@P6
zsdBQU_NBN*6wc1>anMnM!L_S7BcLLxE*vnuv(`TMEUUy4=H9dtu1(SN;m7Kag($h)
zxH9FmzTU=3(l*Xj`4hm(852aH9FJJuikfgj2GM86$yFzMrKxRmz}4##oC;qDwK6tV
zP@0)EVmc?^h`9PS+=+d??#<miGU5pQ#HbchMKXKKIA6a(ht<?_d~A(VkH6nT5!I?t
zeKlPJ?=-*O+4{imv#>`LC3yHQAi{XDy|b5BrFsIEf)zZRds7*21>mMN{fXF&o{G)8
z2Gg)V-deRF-pdF!bggK)Tnu~5GPUxDo)HbeJ0iCMoH={aa+MN)T_0eTP_`lP4Ie~n
zu(28wW+-`buGr&f_DSCdEOl<u++;Kv?4*|vNAm=%dA;r)+2}jGJ9Hzf1*r?TM}&1U
zPH3w8SA8p~+&yt#ax_Zk%6LJj+Q%H;%gE`}wB2>b7|Ht!Fs(E{G>+jIUGj0PMAYAt
z=-$wC+>zUT8n|v#(&uQ1&=1Bu#DqgdQNzi<M%<?I*e0#(d7`N5Z>c%v-F?49xT3mC
z%Cz=Qbu$abF9x?Ns7}t)9B7~?w>sxJWCjQ9Ftb+^yKZ-r<yu?uSS;asMe9G)p+|0$
zWJ4`dG<M$hyaFYT628lECn}(4jqB^Zmm?b(DsU!x`9e;Gg__WZIsSzSD;(v_(BwGA
zFavY}JXv4sW_8HWuv+Q4nWFFT>7gI&-njp_I}7j1VuzG`Gt}@!MWU^^*(czU2qmGn
zLDL$u3wv`GRZ0e~`^&OJu%4qekuIq+M;vqb^|_Qr4S(RlWvGlin*(a>=tW1;J56L(
zG~kpb%PeN04oVME*Bm5aK)aMjx+-QpY0>-H+g#La3%9H9pb{}V8wOQZdlbW4*b80`
zs|F&*?t}vcWxG<2I`TDT4)a~(MOYgZ0~qiK4^H<?^$C}+*f_&SZNizV-Uoc%Dxg-j
zl#>7%G$3l9hgnYY*BkK6yJi~NDQpLwUD?-fAcjqoG8aXT5w_k^A&d&phAWqbyBw+$
zE)KcJ!RUMSY)+MK);u_Q1DjA<Fn9B<eTlVM`a6)(PybN?%Pafr<X%+?FbX-F9q`z*
z?75ze5%>Dq)qN%V9-*)q!2X8BDAS-j*;K<xVXk+I2x(@>?oPs~68dag&Skyhv8ca~
zn6I<Tz_q)nZezj35M4zE?WXrx9g+JWfBWg!N2szsyId7~o?k~Ae}9p>UMesg)-gSk
zy!oZbwip6GE8b*MT7~4fuO;o~iwYC=kVKZki=1iT0U_D=lCj4zT>rkb|M25iX9Xpc
zL+xz9Laz7rD&v5}&9&(ve{8C`-}@mVjTG<6z|?^T7sRfw(+2kQ6zR<@A69z1{@`GS
z6{3&~q9}7)#y(uo`4}#i!R2txM`aj8%Em0{bQp_y)3OlHa6QmJogwDdLHV<pMTi~2
z_P^^0rW2wE!UD&3-e~9y9J*Q6=3=+&eqr8N>>-2@_Yq(a8Sm{|0A?~0AS{>?)#S()
fK+p<!9*uI!47T<J@c}8q1m<mmvZG}FS;_wZs?8RU
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..d6aa6d7f651c924dc55f8aed978ce69888b4ac68
GIT binary patch
literal 36864
zcmeI52S5}@+sAjWaYtJO6(k5JpcE;4+ySSkp%($EirDaglV0S&8bMAeHV_pR5kWx(
zK_ManDv7ArutXF~5GA%CAlSu%yt{jcATgiG2l<k`->mN5vs3;v``g)vo!PlP4>zZv
zXg+gZL{u0rn#qRbAQ*<snM??Rq_LHOtvw4BGblYf*eBe_{ykF~GBA58gLFbP$!19E
z1yUnbA{8v9i|v2~5C8-K0YCr{00aO5KmZW<3kjIhXo`wzuy;_nKYx*52rtM#Tp)0b
z@eK{~ldy8PvGBBEI!v*)na(8Qm@ZS8pW>O?JQixCGl@zlOeGXTdA|J6Pbp}hDG+h}
z71%n~6q@2UYVd3#zaYjpnC}<;NrC#Af;hIn1`B2BSdeLoBYKV_NFa#eM|~2Zd?q4_
z?61LP=_I+p5{hbSa7tJ-&o`9+Df=gbB-Zh?uynHN%OS|0>FVy_Y~k+BbhPniPMzZ5
zHq{0%!OCTdt&@Y5C)2{x#oben$wRS~g{?-|%EoOX01si|L0o*7i|=xYT|5bwgKv!S
zjS0Rn#W!4hGY;SI@Qp9F8AnK=qAj6_;u<VeR1U7fLU9=uitDgYT!@9@N-PwYVxhPe
z3&q7)s2{!+S5H(wLfJ@U6Us(}vJs(dL?{~(%0`5;5ut2ED6@&YY$7k4$jc`3vWdJV
zM63xBYeK}D5V0nrSVGx^P&Oq}5|mg3CL`i_jZi$V5pldmM4j10IGYG(6X6^pghPaI
z#36Vo9275wLy+MRWH^LAhtTH`=fRlBYfR)dCh{5+R548yHDcj~i%q=tTs#yd68rIu
z*&Mbpo<MBk3B)E|y4b|4AU5%%6`S}Wi%p!U*u=4lOoETt?9E1y5{LF?GwG8JKP93B
zroDAA?Wu!FZ+$Vdr@olklNK}kr0ppf3+<^dX7&_}nLPz#W>3MGi8nR84w#9zH?fH~
zI<e{BYw~-|d7n(8(TLLe_EyIiua3Vl5B2v&Q5_bErf8!ELn7BQKNRcH`|x6-BZz&U
zPuH~%>yt^wTrC}Uw|J)>M&9ElL-c{0U;zXG0YCr{00aO5KmZT`1ONd*01yBK0D=D=
z0(7b*b}$U21VTEI4&)Kih+IQzkrPNI76cYR01yBK00BS%5C8-K0YCr{00aO5KmZW<
zn+VX!6j)Ox*@r<G(qmT9#YVCOVMsQY(!|C=Dq%2cexZnsCNg2rv(Awu$P|VqJ?kvd
z|CgzNWGax+vW>Fovd*$pnJT0l3HzH45EKgt00MvjAOHve0)PM@00;mAfB+!y8376z
zf*E#{csUp~XH7yU(8yCMWNBkhz95<z6%i55w6egz)Xj8_5JU$=@jcv}m>wQZ%qTv#
zBM<~dgfpY@q=Fc1t~h;=bR-5zhc-w$)Irjr43ZAnff7FyMlD(9=p@nkG9}K}A3Rn3
zs0I8erXN2lT0pYLlo*4dMD+h<??FgCl8B6xZ9#rUCdqanYp@`&00MvjAOHve0)PM@
z00;mAfB+x>2mk{AA_5X*d3ZN8gMp8j&<E55_X`c;hewNg;1amppbb>uVw3^ZP0(j_
zfQ-wLMw49`D3hg%s^DV{Qqsfv3<dR${fOrO$3aLcQh-z=4aik&?*C3C35oj`onlZ`
zKmZT`1ONd*01yBK00BS%5C8-K0YKoNO`vCRLi|Y|LLLK)h8cYPcm{q^(Gubq8bVfw
z@rfPcaH=?*g69x*{>3q5aSTb5JcS_;gc^!s^}}L9qk|$t`TD%jX#H?r7+-+vi~9eO
z5ON=>LG~i6kRl`nX+|QE^Z#scpmu-&AOHve0)PM@00;mAfB+x>2mk_rz(1LQ5}5-Z
zykyfFexY@d+|eC0$vKTKshtN7i>4>wv7QmE`{x#4m=bjQ$>{<r$?dlL4+})I4e(f6
zk&}y+h3>F68`8-ZdVbgZwSEFBF?|4!J-V?g;`vV=KE|^Yhen=d=I(7?28m_^D3Uph
zFO@5U!J<N!A*pqeiL{ASE@u9lm*jcW?<hlMW!0gzuF%kUIc}eWiI?N_+4~(+_IJYn
zSg@vK^Fb^n_7jSIhH6kDSrrwMXgOpOOd<`Hqbq84W}eKecb=@h%{6qwnffUns0@vv
zn`oc-Mh1qZsL;T_(u1lh%3!}7!ZI>tv9aI$;LK2z;h3Nt7Dv202qg$%^}f}HsSsHR
zBM>TtB}qbzz~S$%KVMDBm@k}QU?M!ymO@v!eb@f*%S8*C0%-GPW;Jg0%7vBBqz9RH
zo;Ye;_}==^syB_wnaf^8WRsj0n(-3&v@EmE4^lo83JQWo@!n}(d!&@AeWJ*<?Vu?`
zW04zUqxI!BrILvKF$t^QQC!R)W+&OInFZIxN7B)XgqB&GFLur0UQSuJx~8kGw7OJz
zi~}v?sgZxto9eBph4xn~h4ouwrN@@S<7?C=hrkEpnv*mwt=N-x%{H{0Irhl(4@b@i
zW+|LZy4BS5bn!UtAsyG%GiU*@CR<Bs)3M{^qFrwdca<2*^DiFRYG?a)YsGikD%F<$
zJ8#opFZDGGnDa|aE{P07aIPFGi%Me?a7HRns3;kRyZbW3PBq31h8$5d(&l#ExY6qk
zUbavi?;b9{t9dKn{QZEWfMa8Wn;(azFF*HmRpYL@Ouc3DuI8)*Z<L1%^xLRiyA@xT
z%$*#T$`&Z6#@4==+C2A6ymI|;&624VNq6q2PAju6%yTMS$+b}5eJnC}a&1j#N6g;W
zYsPP1nJJ}wy*V<`=Ss<k1tzCnv)61?Z40cLkZ(wlEbltfpt|(lImsHO(3?tUh58Bx
z2d6Dg_^`b6O^klF7v1JvMzOck&BVic-E(Bd?W$Cp_x=St?Low`wC3$UU(sAVODHv6
zWqGl$?x;7N$MqD)RA=vWwo9E>RI#!tUhB?V#-h909*fXCD5hzlT>BjAa&jYnaSgmh
z{?4T};IGGh$>qXA5@iYQXyoy_$IB5PxcLW{KksISVhb)V%h7{0v3I&$^04xNA#7sX
zAb^8m7?X4FyFxXA!ubt*H{5(8d&{s@ZNtKcJLk079@k7;UUML_$uVxNMk3{KvMrhL
zwqu~R2DrEFOCf&fU+--hHBA^1>;n31037qTGME>7CGxj0-0zATIIpid*FpG(A9eu$
zvwOWadBKSAfQdh>->>^5RL6(ky8j&G%z<zP^l6}Ri%oRGd}`dwCM5&*hA}cKcRqfT
zG94)mt~0kRTxeGwiZCv1f4)gu<G{$+ZT0zY4KF2?O;`3dZuNH?XHwTSVeO5ykg`Ub
zLvF1SWu^7DAGbYzy3}MFuVu*dc8yyR&J)R(A39|`vwvFty5s!ElPxJ_-hm{W_fT8D
zo+aN{AGU~??tP`pbc=qF&4q6Y9KHOOr(E8A{ak_0JNvxj%ddN!-sBM8VIQ?)Rd*J}
zkn-`p!%H2$7iY;y<LBc>?D@e_Za3*<+Y<d;y?t}-@~BeZY27`&tg!ArJX6>?b!Sdv
zctTSC0WFHppK!0<vvf>0b=jL**@xWj^0eAJ?d$>NX>lQ?$sJRl@vrP3l2sCTV%K4*
zwx6cjY;``{tQ~DV&Gg74NsY&|u1FMUci&oWm3!;kvsWsWd?Z62RcU^k5is{#PTKeO
z%h+^E<_(i$*ITNKejVbX!po@5Ta@;)-M!=8=9!8+*R7eqG3}eCEY9QP(Y$BBCLXdG
z^P*vOfe-T+^{O3Bqr5r-A2b+@Tlcf&rEF9FiS*N{Ds!6}s^$&5l<@BH7Gp!3xBIGQ
zl9OW^+|)<<+)v<dD{K`?ev=g9BCMb_mK@o*@?7kQ;Wdi0?<NV0O&x;L?@QlJKR-{|
z^2F$@6LPi3y5^3nguG&?+LzpLw=wWmPr|OGL1@@tx<!SB6jC2=I<&7J!7Kl@Wq{rk
zdmmP>HywmSB*B#5`qRp$v>2cKovwFt>NA&R>#J8aUud|b`O7<{4{h;z`77r=upb|l
z90@PQ|ENrX)~R6r^fL~wf4w`!UI&Je#@*@Ho*H5<)*8E|)f2l|N`DtS2oS&OWWPGK
z`KvDUhW+~8uYB%B?n|+rr7!5>UejHmfUs_!2|lrJTeZiTi*^17H(kh;jo6XfSTW&F
zZLqR%WYY=O;ewMoc{w*8>n)bClhju^`pk4w)T#BInOkl|c?Nn-6P%=8Em&+VCHdlF
zd{OzwM-}XeGI58hqM#}`^G7*b4fA;RcTZI0$a6ds_Q!9zaQ8`WT}H<J9Ez{oh%pW;
zTwES)Uu??bzR}pXKC!B^I<qC}n0Dn#=La`arV1T*|By3LS9x<pDo?O_?Z-*Wf?qvd
z=xuPUEo7~WO|e(h@r&o)+@JAMAvV*kJETVYE;Y96hI^6cnLWjxdTBbvEZF+TycpYT
zmaAWjuJ;hF29XO5K>4U2eup}QG7Fs{9EtrfCd;7b8L>fRfIik5@cH5f_=bo!`u%8S
zU4w-|q5ginDF6Oh`?V3`#@E2BU4a8tf1vN(z!_G{ev<KUh4%(42~BsUwWl{9*1tUd
z+{J7DyGA%3eC>OMGCXW``5x;)_k~Kcm&A>ES@Cpc%sQoGPm~>PHLjdmUnPIw{rXdt
zt~tr2mp0ce-D(x$ybW$>XVj`peqDZ(I%J_#wYiqf3x}~gR!^I|`(s!4J-cDjm5$9n
z78qU}JKZ<&2R-N71t;m*H<dDvaN?5Mu8z+4VqUF0<!#Z#W+^CzZg)QUywPBf-^T81
zlVecLjg>W$Ir92TLl*CCxbXNWRb5w3ng-E^Z{KQgIAFW;%rnJX>%%DQT074(-v<V}
zPiWNOK9-ewrDL8h_r|z##;u3V?Qi!HH*|yHR56e9^{M_}>i)xc|DPi95Rz?{%w}xE
z46pzKfB+x>2mk_rz<&pU6-JES?p5DI8Y<6$WU+tPuo5<uCBIS{>fiMu;`(O!(>DIL
zVk{Gh7_7o`V`EldycNMlq3}5D|Nr+Jb8O%k!bTYBy(jJSv9&*P4hGmK#@;O5+b16I
z5GwZI<F`HHF2&i1?i}-d-LF!}dff3J?x)61R5vQgA9*2or2odeL|^`rWy!V@f9nXN
z^q6D}Hr6mdo0Qvcgpt&*Ycue%mVTXI<If-87|;0M_8IzqgZZo7<D<W-KChICU0$qF
zpL+xeI(<Iu`<uT6tTE$ft0L+A&8we}^_>;fDmN;&Y<4RB_9H)iu5P`qcWAWXhHG{$
zj7{w~nyFW`Y#&y)Yx#$*Sgl@iD4`)N;$sNH{Ykn0YNq6{A}z*_7iBYsAN(j0x@-5V
zvWZSkuMTwY^|7>8BwO9PIP(#;5E>#e>U7~Owct8I-erZ^9b4q1eP-5e`F26&q`W0d
zp1GQFZ^PZ|DOsZ+yV<VX*>lY^OWg`dTiWCz!>bo$Mvv;eK9Sn(v^}jhDkh+M3x8%t
zV_R&sld9>Qx9Yq?m3^&B8w|7kvQ<auh6KFwOyPaMhoWT>BRpzjsx?}w>rad@s+J}_
zk9^ly-Y}cvViwXt3e=7x8%__H9CoLDRaUCaZk_iT)#F!GT&TVsD>Y81j_j<sR|8Ta
zS+;8WIi3%DxAo?O%q6=X?29XsDRfAv<0i{A8*hBJPM(`{<_Ueuo>S?Ti<>2Mauj%G
zkRMmmCP^56Fzr@;Oq1(%1$iw}dRc7xc5PHBS>K+(8*Y_f>-avFR<@p`9$ai|^)`6M
z`V|!pt``NN#y&sq&GbuBo7^tD%<RZ`+0{iql8)|au?yILrTA+2O*_Adu1Y3Dc6!!*
zyJmAvgj4<IHuCF>3IY3~4r#otTSW4u7oQ&eJostuGw;crs(R;K{mNvHmyye*t1K;X
zhcxh3k9mo~XxLwRfDQ|X(fWGRzR!jK54<V%R{p{CreA!9{96a>s68*}&WiGgEZau+
eh2?2K8np(E2++#k6Wq>9JH3Cot(UQ))qeokoP;$1
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e740fd73e936386e125ee24cca039e79dcc932be
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1d08f78285
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx b/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..8ed37c04dafbbb1e34561794ae0f876e0688c115
GIT binary patch
literal 3325
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
H0s;sC!G=jY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..bb19429e5560cb66b93edd9fbe4f4e5d93e1153d
GIT binary patch
literal 36864
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
V*0@6&e6Pp6#87nXFD)#9{|D0SSLOf!
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e66a9a26ae087c879a16a4d666c9ac970fe3de48
GIT binary patch
literal 45056
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
RBoyDtmNG0ILbp9Y{tp%$q~QPn
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..78691fd862
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-no-names.crt__server-no-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-no-names.pfx b/src/test/ssl/ssl/nss/server-no-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..c35fcdabc7a2a7859bc541547a557ddb7b5cfee0
GIT binary patch
literal 3109
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-password.pfx b/src/test/ssl/ssl/nss/server-password.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a6ad1bb869b043715aca18b25791da12403a6be8
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3431d1549ef5c2c073e938de6b106f535747b9a7
GIT binary patch
literal 36864
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	f?a?`<~)v^C&ynLEAd?LgIW`
zv^1&Nck9*8*}Q8h8`oXvY$+};))?hP3u`kA%70tFJvGnq=1Fnw_IQQS#qgL5+RkBc
z(Tb*IT|0Zu_#8h|yIG@8`FuQed0v+4`Q$r|jctpq^p&62>Fl5d!@3+j^)08*k@Iuj
zndTIlstB*1+CItQ-S*OD`kLi-L3`?D-z*KZ4xar}++GqHhTy$QC<9f%9@!bDN}-};
z81C-N3_H{qGZ=C~tw~#34OfiZWb&$+;_~45i3hrOf-gS|P7XdjI<)CYL`FhY+uDYl
znoOhRD(*Jyqi;0^#~8OzJNGl+6wYyuNae(6q{d%)Ikjod#YBzT!McT0OOx+EOr2IT
zF>jx1-YTB0&i>Q$_c~v>(D6L(;G6YhcCE^k*QjfnzuNys;l~9Q-@oCkPuFUhcV_G%
zQ;OV)&Qtfamj0_s?t*&wZS_lHV^x!)X^WFSCKSJoGtQna%YVP4V211M)yIvxXDeFe
zoYbEC;Uy>SXVK}jrd@Y$=q~mZ%ll|16a*R$f7@}+h&ie}d(Y%asnhaHS2ZT;-G4`4
z^g!Qh5xNhGYh0+*I-8n6Zon_D{-2S*acTAYRl2XaXIM<4EWsU(3f}ibCE^1&|KRfH
z-HaNx;NlFf5u}U#{!6u&y%!8&&&mw|I2eX8InTZ;bYY%2>fXW4x1TZYm_F6syztSU
z*)0y|bkh<p9G&0jvSP!C)s*9F9LR*X9R+PL!M$x?3W;iey|*>!Qo@K}7tm)Ba?IZ<
zVqWOA#NWd37gt>WdHt?)9e}q1u>t(w?)6{J3x-4nk2|~Rh~cwv1ApPuBUSW^M<Z3y
zwt42w{Me)@>WWv5>L#4cqZBpocMVPPL5f3bZ0zzDPC5~e(5rW~Z_yudbXfe(+C%S5
ztCLH7G-jAT4f3$EsOcQL;Z|B$Ndy0w$5WY-;#!BUoln}9TI>vHR&H+{aYr<H9QoQK
z*Bvh$+fKZBe!1&>b4tmKc_jV^sO6B6ozUDEwiWrzxY233%{YYr!_dPn(*+4B*S6MG
z9X5FHxbIv-omb@+r^x4y(ci4?&Z3x7x;{9)G7wJZE;(=BZZ+h<Sr?`Kr1LFHjQ1Ml
z&Y852D!)wcLFMwinuqW#amUm>yBi{tk`Eo#qxk;`_d3JZz+y|MqouuLSbe9r{lRIM
zj%rL>5mvnB`P3J}8%LD03g=bi9G7poK9#?H@}(yI*oo6DPd%0!@x=Fr%whfRI|=rC
z?@YLK<D|O3T-f6?x)XK;&zZnY`_^$eN0yR#%i?rhb9w$R%5ItgJFe_ol=iCC^ZEO&
zvzU7}u8&Gj8`_w~eX?d`z>8m2ALEaDd2iief7VYrXTE71KK=Q;pYNGiZM<t&ooy+s
z$f!)!oYQ#k%-liMN$;O*GdJbG%RMuTye96Rht4qnhe^Vnc~8Z1LzBbY#HF-`!c*z1
zs^W(XzQFW*kSs2+bPCCMsPG`;@>~tOiji3rN>@&I&apZPO^>7MS9{=YqyJ|;3A>U8
z;Kcsg*I8IhA@%X5YJL3(-ud5K`sq!v-_GjwrUNiU5={HGKh1V?zMbVbWy6@(pRO;{
zxmB;cS$Rqz6y?vj7|p)<hPLDC;-K|wCc=b2<v|-1Fn{_PheXZ4-k)N>9fqOC{pnZl
zD`H<_KVp9AXKZ|5`qTlS_PbtnfS!*2UC)}cvbx>7_+h+3W1A24*wuC``B@8PRJK%T
zZzz!GFRIb3RPlSI+5Y5Z@Y!^YE_=7#3$JFMsa8L(<v2D+v3kjZHi6MEFUCdjEmu{P
zZt!t7x|FhL{r&oDodv@$zdUQW?{?K5qo@hvyNZKzXD0gI-4(0vsj-85CElc}tS(j5
zrjU^y?z!Mb(f9|p)!*w~ZX2_(_`I;_k!NX9Qde%~+Us{p8|7l$Do1Th>fS_sRi!Y;
z?(xw5=cX0hV%S;kE{n1`v?MLL{+nkHM%beT50Z9w=5`!)F0!%I-~1>+wP;i1Vyn9Q
zO_@B`7f(^OM)|s}`Q4Z5XdjPFvl!$RkG$UhVnTV&Fv7Ebae#@hsVc7z-2B+#v=Y+G
zOi8PLwq(oMXNqTP77z0<jf@I9U9nWTeok88v)y;r54*nQm}teGYq|T8(l^R1-EwAX
z_S&C@7i?}Sxvm#->o%`mUnVU_V4U?f&LP*csc>W2q=IMD+DFZ97Oi>jIOkj=MJ=pu
ztMBEBjFdHF+**QOkI7%=O<pRh)t`O$k>(Wg>!mGo7w5{HwD4WNapd(*A?;1u_0;P&
z3)faxblfdH?CZQ=t$S&P;nL~096_dGnakPPw9%e#Bjk-cZkpCy+EuojVL%OQUva4|
zNcZ@6?~7f>8))wGt6#q|r*oM+xr!D$*9NlAVf|8MrD|d{Q#HNluvOwZm+T*LH#86~
z5Wju~=w$xd^dH9O{}h==5Ti*ho4yk>zyb&W0)PM@00;mA{|o{v&FH;TroKZ`Q{h4k
z>>m~?!opK3s}!Iwr&B~;-ztCF#(%9Ct3)CKP<U-D4)xWqB3NVz&%^%zuYaq?g250L
zHI(f=XrIT+{>U-t=R6ntW$E5|Zok8#*v*Vz&vHANeu&|0o80c#DP$wwn2!%r<HzZk
z6&@P)L+G%e^nI%Xg-e#Nagh02qlQN5PcYcniTT-+xL-sKNncEW`X4{}Vt#A>`7Igq
zng8FJMc)^le|K(M_KQ~gNyYetf)TZQPaz?dmm|Kt{ZsIIYhkt)k|Er>u6=Z%Z}d~8
z;qfJYsj~Ht1;#wXTEiLPv8J1UoYYL;(t4|jdPC3QQF*IgP{hi0I)%rQ?nQ{Y!Vund
zjoO=;a)a{q=-<37nK`(qOC~&L|Lc-*uCA|-b|3V&o5&>F|Lf|k$J9JXS!Q@;p09Rj
zP0YS)s(#P6sl@uvs@XPS!O8LamMnSUZq2KQyEjp?hC`G5+<AU;Y%+^I@<`iSl;%g4
zFUX7?-cdJ>+U>e4?Mif9aQQajtQ`$4@#U^smb2gK1mtPvK2_grnk~rI8e$k0{MtJu
z;M)TfJ=-{O8Q)TGq<rU}h#G1YC%4ak-*DodAJ@$~>^W(k{tB|GPq1^u{noWvsr>y0
zA9j?FSy}o+d40URl|c=8GV|aFNS$Q&R9E0~IpY2H+dpS6$@w{VMZRL5Q&J6Yjbf8|
z`iqS!yxkX{$xb=&eTLoQCK-d>ssYxJfTzn(7DpDP-8mH3=w7F)qDRUoiO<-jkBa4L
zTay9?+aJ2(@*$p9vWcV<THs*+E_CLmm8DMZS7XA>{qG*k6eMdqw=$MnpBlqhm;W89
z>_GFR;3GE*ZbsgoBpBzeZlS!#yJo`rt-D38wOd=rZ?38a=SCkJ@vdeODNweca%6jG
z+uj#5oIA9Ps@w%7iswqmClt=u+2IbU|7ShsB?jWe{@M+6SUiZ<*PHge*ZUvvrr59Y
z52QE!>V4r~J6IR}xgqygo)Bd@G<YsNk@lV0(~u#-dWQ~#wzAVIk0dxuH)q=a8&MsF
AssI20
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2118ea3a456ae43f7537af0d83c3cfe04959e6dc
GIT binary patch
literal 45056
zcmeI5c|25Y|Ho&{*v6PavV@FDS<~!Bi!3RMWJ}pHV@t+1_PvRa%3eg$q9md$r4p4A
zxrtUqg+j}%B&4$ZW(K#ryZf1$*YiAo+`r%NoS8ZEJ=f>DzUOn^*UWY1ob%#XS(thT
zx}n^C>E4t;6doi70)s(%C=>_;f-_$t%-7r`zzm>s3(R-$pW=TN!a-WAhD4F$AR*Xl
zkjO`5y-1M=MMM$S30n<2_pcfO<p2SI06+jB01yBO00aO6|4afjK|yH+1u#P+kV2)o
zdAkKt)=>f}bMHcimPY#4Mks5215+c^Tq#P`(-pPF(!@;Payx31(RP&0W)lk=Bb51O
zl%e_N^`<6<)+kG(EvEX0Mw%$*FH>*q%o|Q^g^+->p#m7>>Er4a8sJZ3iaRJlfxhhJ
z9dr42VCN!X0|5bPX(g~dyMl`s#naU%AYcpKGnf+SCOB)(Wb-<sZ78<LvM^r(rAonK
zaB6z|>>Rf295f2mjW%CJ;HMmH@s}KUwe?T|X*nhEjv3oQ)Lm{af%7@|f65UsBma^G
zi@~ZHLIk8`=eEW(ARx$%KA#5qQ<@p|mn>wgT7o55P+Ca|ob1c)viSt_5#Ma8nQoge
z=cVKH4a_aAHBl4{^Tq5p3Xb{0vto7us|3p`BC{6BtVJ?=kyV9EV9kiE8HqL1X3fZ~
znGS14Va=$_nGQP@W@gULh+*ZxVrJIE%7VqP(qJ*HJXj1X5f;PBgvGE@VKJ;+SPUx}
z7URO2&)S>u$IgtKiP@QP?94cJW*j>+j-46D&WvMc#<4Tw*=_Ocws>}1Ji9HP-Il~w
zNo<wGR!MA?G^4UJlh~QH*)`deuxv~?_V(g1thPAz_Tt!{@$7OuyByChC$LKh>=MFk
z32Q9`3~Ma}HW>n&41t}Wz|K!#_XCmLmdI{PWVa=<sWNk7FgPr0<+CBnJ(*RCVOMr>
zBjO2oBCEn|$f__KverHuvMkJotgW35S(`i?vWU)xEUYsjo6l@G-;7OawsgK3=}*mA
zT{2Tad)|ZgoCngpUuHPxml@90WrlyMJGWwH>6~9?IJaVEIJaVEIJaVE$oi<UJTODn
z=WRA*eduOG*ZI(GK6L*hWPfO8>Qd+JP+4|di4=@06@yX3^9e|AQ2JK`<NP;x2X;P&
zF!v}zti6gT^92^r2Qi<(1P}lS00aO600G_!WUK}=AW%sJg8bJuk_>qQ0>peE5Fijj
zLLv?hl89dvCo}h$tIyO~)&7Gi4z8@lH^@u?0fPeqXe$ET=)rFE<#e}TUoSUTEf;zq
zD>-I8b}>d?0Ad3bfiK+R8xZJ0ceAoEMFqMA1fl|hn8y=XWel37L*Q~QzTV!xKFfWW
zhcjA!9?O|oS=F>yGfCO>>`V>woX);M3k~wv(UN2XGcaocod(%4pC@Bmhpj7KN2_%Y
zZR4+O8P*Z84-u5eyrp=cOvEVkfqa96<B0t5yDuKK8H=V2&5wt)<X>-<xwP1L^-0mz
z@ZcdA&4JHr{Ef8ZZdD)Hwo$V&IduQauKSZ^O836hsuy%LozV1Ot2<TUan6q6@cd|?
zs^y|1gyv|irfW}9e23tNGH8~;?d7_!^jmMM+#6aMQc>?#-e*}|zIW<e?!LBX)!ncF
z^M+*y_fDtsk2JxZ4f^Fy-`HCA6k(u!{F=Ys+3>W4u499*mKb8nUhO?Td2YOPW4WHT
zYI@)91?8zeVLDG<zRe|@e*S=w>=t=)xMaHViNHkVR?<SNXyoYW&ued<WFDe{F?+zV
z7;gb#^?2j>ucBanQH(Q2e|`~;AwW?OxkmUY5t+Wk;%ZT){E$VykEIS{cfw-ovzG;%
zXXd`;1B3a%C=5(MXeJAvFh)*5*g4)9>;{2?)u9NDb1faO<(q=*!2CIugU~%-vBt9{
zO?6+E>0fTf-WymGtnyo)BKUhI{_fg_IEBR1*54jwz%ndgj#gAt`0>m;WicTix}=`z
z$t{2NAT1{fr~m#*iA;3Fv+4{L-}Y{ISFutPt3zYi6Ox+JBMaL`qRo1mGU`wpo0Y}S
zf8N{j{ml4^f?}LYeF(|X%Sru7({tMm-bn)bWw6uR&x`rFniQF7E7^`$>X=?H_`Z<7
zv(2uD|6??2BJljtI}-7J#4<}khkAI9vB^do<??odc`rEN_ayk1ur#3q-REux-@(92
z+f1~6+kfnHqZReSMpP@jVACjM_|<wz>bjl14eO3Y?vhTZu=7j|{Lw?ydXo{hR?a#6
z9pU4hFuv>X4)Iz^uyPTz8~C_&!-ar#%8!NKoP9Ilp?@L&&c(NR7U<&cH3RL1S*k=;
z$8@gU@Z)Kp`R&F94^0^5J|I6RBX#=wYr@Ap2I~)Q&R)?vMJo-Wv=(12H*xU}!I&G5
zIX<vCb)@lC0M`8a1ER%LlZu2M)?zX=O@;sIc!oLo-LBlhA}i&yi3;M(ZyC|;;t9Re
zKe8VpUX6z(6e`=k$f2FdIq5m}0VLJdpF$|SR+rn*E?x3tBuYrH(LZa|r5$VZCaS}y
zN2A6A3!)YHFEHA3d7+Sc@x={(dd!`6drQ8(s=?7_W#%iNsydzNCs%eS$yE+;rEvdQ
zYyPSuX)naW!0SJjF)4gY?fs#HP4h5P@isYpV`qukn72!zhwPj2GJb4#U)C;=zD@tz
z>zRUYV5{_g*rvXIOYW_-dTimW6DGtEw!C`PzfkfxDM-2g-opu9+o-Ktae0)*aQnoQ
zpXB;`9EW|1)l*|etwvf`8b&Vo?lTNl>x?_NZ$x{8y6>Q${0iCu?CV&2QDsN#w=Ra$
z-eCzHS9zh}0r9NN^=tib#r^({u;vs<{VVwll;R8V0;9<4%!K1QTq!L4g7DpXw1-Ib
zbQ#;)z@$){A}Bc`wdehcv@IXK&dOsFLYMKIy!}ypenDvS>d=GFe2&O$F!U79qy+}L
z$jh78|A1bD25I>yK|X*BuO8HVtob5V<DHPSQHb}Q<jPvb<EqCT>r?!5g7#!9uS>b=
z02L#+oXqnR|GIYVTl2yc|4SMW^{0*pOcx)j4~yyLhi%)Ycq>mQ5JuO@T2H!uiguP4
z3OOxRJMWryRYlh(ruwxNz^*jIu9lW>37~mpHnz7sUc{Axxq#pPC$39s(jF$G4%#y*
z<XNA(v)%P8|CXpXp?EzFGtWu&UE2&Fmw0%*iSfV-xQ7+p7&~NcmGVYd6I?a%HZQ_n
zu1joU)ymgRTG64OIxp?utJBEJ&O2#%-pa-LbYe%}H-=2%s~_*jvZjY`o!DT1tEk`1
zL@ruSNBnGOhNP_WrPq`FXI%`m3;I+H0%cZW;P+#fRvJt7O!zr&zItKXdR{1Kht^IF
zwjIjXRRgz=#|~*{=ml=8yk!^jy-V|ho%EC;R|?G=8*A?pkDR#sc6|^elS#oLKs>Tw
zY}2~HvBzq^o9#6=$}<-Zl&`f`>Ph%rrQbYKY-^%>TlB@&Fk8M;Eg>uG{Zp^nUUsF1
zs?##!-e}aHQgXERynH!4IHohwdn7>N+(7R6`iUiU<oNVCvzsSckBB<OeZP|ee$$t9
z)8s??$>w3=c-ul}#4sfsFQI5@G0d>tb!TKz5?rgR;$5Bib$!<LYw-W~oi&UFfq8wN
z`2;3_06+jB01yBO00aO600DpiKmZ^B5C8}O1pbc^5QIR5Sr<)aj{he>$cg`B8whL;
zKmZ^B5C8}O1ONg60e}EN03ZMm00;mC00L|R5C}+k=J>w?gscFd00aO600DpiKmZ^B
z5C8}O1ONg60e}EN03h%WB47$-$P53s{+My*_`e^7?EeRCAz%d{01yBO00aO600Dpi
zKmZ^B5C8}O1ONg6fq#R5EQBol_x~7a=J@|D2>JHkNDDLp2mk~C0ssMk06+jB01yBO
z00aO600DpiK;R!mKpH|2{=0u%%+GWQM*d)V6`6VaN7)9<00aO600DpiKmZ^B5C8}O
z1ONg60e}EN03h%m6M)IEuK%luP(jEM<VvI*@)9x>`52jn+>W3i3=l66`G^TbEn>H5
zqNpbVD%yZpBHAZf2rq-DifX_IMK_AV;jJPJ5voY7$Z<FcE+g_?qzg`mTmQ$#fCK;m
zfB--MAOH{m2mk~C0sw*kr34m3^uYn19y?sz=z%+M1e_ZN=jOI!=H}@*oEk?NqpAQ|
z%Wm!E7B<(|^^e9&IjI<BPAX{$CzXiiq!N@ksdyA8m8{4~)n5Ep94<O|Hwu#-`yT2#
z3LIs!JSUk-l;b25sf#$tRDvugnMjf0B$F`Gf0e03GFJPK4U^(1lNWN5sYFRmGLgD~
zlT0N@aFU4>aZWM`BlcHW$K4H!{bR$B9Az?slT0Owa*~NuI47A(5aA>fDKJhl2_yVh
zne2*p$N#ZmLL6nXASanh6yPKisr;N|Dgnw#CQ=}rWD<r?1fn49M+pcB@uj<pGDlSz
z&|VO78=?Y%5cP!LhAWDM!ydyh!uy5#h4clF3Vao?<Uh|Z1a*PbLS*>@!S})H%m)9>
ziGh~k*WV)n`tvaP(-)tDVeG~@%-=v5fieH(e!?Y9=hqCJANuq$q*!?MDRlQ)2QnRR
z9O-FuOZ79VEgttbH>~{Ay@jEuQRL>A%1(>4O<j7In{~DvGKP%p%~<>9dwIg}Cef4S
zHyLPYjwVSM9!y3QWTrmzDpJ{-uHAXNFx{8mc~sBeK(y#pt)8QJf%b3QOn$4{@OyJt
z?r}#uMU7``8#@Yd{n=}!7?0i#-S4lAu$W+=rG7E_^GL&k$vYihS=FQA@T5=yBh$%c
zUr$D4K7mCHN@R6khxOl@Hs@yY1^z*#YSNjwXUoZDOA~{qQ=?Z6iw$30jO?4F?07uk
z$3QRSY?91_$^Aokk&K?g`~E7uEmM~HqsQxvzI;qfQLA>h5wN}uI>pW8k{j5Lx^Ih4
z95L(6+jn4J)sIitCn#nC2YgjX@AVMxjTvZ3jwY!@9!zG_6b)>aS(%d0jwcW=y6S95
zS^gk@`oN~>QoFVvle8XgCOeHqwN=c*`J%k?TX5>tMWctsB^%25b_=D(RNvN2-OoTT
z;AoOa<-uf7t=UJ(TkjlJfa>?wI3Fy)J47mWo^g`c+GsfDn_MTw&1A|&!@?i|Ez6*`
zf<EhtFYYw4QfdxjMeydcSspcm<Et5H363VI1RhKp=<1!ABIDPYI*<v!^~9f`1f_!@
z9qvtrrAtFC-dBinGug4q=)7XMvB`E1`QYU4C18)&SMN4%yA)In@~VkIti>_V;v7v9
zc^QI)Vxt_pJVtA&7U665I@iDMk|wK%Tv77EQ-)(!7f+n$W->DRLWK%$5_Dtmz1$~Q
zN@mKd``^AOi3hFde`qc%COgDHi*Yo`(-73OG~KpAYr&}`4-NMVmY_*3OKEeF6`28F
zm2(fG2b@~DnKU2l6yAJbjhZI$uDyq;&bl&+=jGH=ICgjRODh!p^Bx8o`HRV)e-rS~
z-8PT9pp#F7v$m*4KE&_SuR5%Lv&uAZ?a|uLyXq<@G;+C_EGk^u$4{m$JO5fummxq(
z^DIz#9<4+UhE0Z3w;=JQ3^anXNnVDa10}|JA+VyY_n^n(jI^BZCD@3;MbyXqHpM$d
ztPGxDa5Jg2sJ&@rOPl8TugzikNnX&c&A!$9_m4dvvhO^7xb1Kc11-wYBu_&S1etg7
z8Grvbf2xdJKt*|2$Z}=4c$m|`t)}K?+(TJ@ZYJgF`)v%6sdtqo9o$ioI>AFp@8sax
z-4)M25Nirb-4qyTI7gE_4M92wzlC<}4Hxf<ik1qcU0eJ%CxN&+^`lTr2K<9@HJ>*(
zlMxM8x}$Y^>Y;QCsa>5Os^6=qzQN>esCBSi4$1y6i43#|N0U4aLCHezibq|aK(-XV
zzmA~PSyNT7Hy3+Fed|@XI-CTKsNrVP)Fp!?KHPy?Zf$b2`IFS{NvE4zBf4c{=s$Fm
z*E}{(W1wLiP4YAZ4cl11dG3=YP!m+xRAI4y#p>fiSB}VhzMg&768nvAP{_^XnW~*(
z?+&Gu4cz#!nQ$sHY%jzARPt-t$?%fR#b2^yn7c(`jwX2;f|jVkgJWr%Bfg}{OB^=%
z&E|eQNpSpzwuZ$S{!qQGTdcX6yjq&O&GY#&Wb4c4Aqqt@E1vEOQps$Hi?n#Mz3GD{
z-!%qW=ogbe|9a<P2pa#goVL%<_6S9-<(LmCO1#(-MEqE=DynW@?h^sQGpXE6s_!7)
z6-%occ<*G?Vq$8W+<tCjan*?NcjtAKjBM(V3j;04*(5JR(A)Bkl7o%(SGSkfA4#q0
zg~X-XJu<0z+3`KBUfO8g0w_0=XLPb&+Y9#ggl>|&v`BT}oNmA9=vdFLf~d<E%K7U*
z3oy_E98K~x1ev+3>fYPaMH`ns{d*rjEy=4!s_7eD4IffG`8f9Ku10Pqop&DkgspLI
znf@gAS}C+pMwM}Fed)GmNp;9Q`8x%Veqf;aIhy2Y2zt>eiyt#Tu-o4ywm4nS$!0f{
z)ONxeYAdFm&5zR7yT#3<k5|IciqrR(`Id%lQh4$}U!m=!p=_9m$>N%4O@pxQAO;%B
z(Iihp(AeVWtcY-|{!QN&;ocY`bXi;P(H$ka`IWKBqZ#dLS=>xozjDd*{?QwJno{es
z!Y}wlQ(*Hkl{00Acb&Gb>US0$V4xu!P4YAZsedYaxhf;D%lG+k;wd|!tzPyt#HyDk
zyd5-px|@S0nz@;@aonL>+vu<*Ej{yI%)v_m7@Eb@xw2!agzS)DJz0+j3^X4{lROPU
zQqPA~Pd>Jr2-?5gd;|5Fvi``r@5J_osjx^Xa0Pz9BR7-pA0*StN#dr7LqjS=!F-GO
z$@Yqqm1AR&51{J%<9;@brC@#q!XC-F<7~tAPXq0bxWzTDKDgNBTd(Pt7XJtggMsm$
zKd2SOIRA1`TX+9zuy3l%At7?aiNe8)uph?4U-L&scdgzTY<Xj2HtcU~$_WTN#~Xv)
zAW*P66me)NWWgr;M~4ojE2QO|go@=%e^pY8{i+ykkzr1EZc$|&|04w)K}dVVC4{&r
z4SpBCL?i~*3DXwN5c(vvNw83mk0}EaKmZ^B5C8}O1b8RFK&$<-r~di!7Z1O~-5Z|j
z$Vv)sQ{B>iW2)rNi=2uro6HxF@@IVXY!Sgk3H{A3@Tc>-;nJat?Lyi|F7;<x=EjIA
z$~@^y_q=UWnGznwxU_lFfq_=#Y?7Dry4w(WbS;{Tc~Dhnb-PuBPHFJ?mW?Vay}!7w
zy<%Ugu$`O96Sdt()C*oTY{G67gDxF-z8pOgQu2K|)aiB=CgH@$d4|$b{<X7b$p7|I
zAP*qlLo0rxYf&VIJ_vnMvi79B&vb7H7N7d)L+TsB>59j5^O$h&g2}F<!MP-t!!$Gf
zs3lMC>V=uSTN8V^=E9LzXg?Z5NtvTZUN~w!PHQdq%Rh{qpkIt@wUHjmAZuaGWKXZM
z@$G3ZaTej`aiGHXz$wP6k=6HJ#&=z>T^^ltU+C-}&z2iE^K?5dWMUXfOE`KY@xbxX
z?XJ%|U)VgqW-O*bPm5ZDsVOOGkd^m+V;7Ph`cj9+%_Brn$DxcgqMJH3d_Qw7cK0Be
zR7MXp?4XZPgmP{vcrujG96j=K;(7H!k+0o@sSkPrXu;MNAtKXtjfci0?j>uji%7D+
z)xVOPN4XaS)%P_oRPNQ+?31nBFp&RLCWtS^ZaHQD?THk6N;pGFiK9nePU%`<W%Qpk
z#-5Ijn_OXH@eTSCqLbX*>(~?Ld3M=$Yl0d#kC2wlhiu?yo=zp@8w}pIA5d5n&^c=I
zvY^snmoiGP{~<#O#nB_4hrV{&@3b-_`L%7k?xv^iJ+V{oq$C&tZ}K{IcSgvfA#HNp
zJRWZJ>`b0qtRG41EAX-XO^Od+l;fp1L7J$sj!!(1@|2;Z$k8J&=Ky>xchrx*F(6#N
zaxA;cPWy2#^3n46C!+7iJkxXPrm<DrJYF7G*rW69^!Ibt*S=yLJeJ}=x5@WQKXbWv
zF7a8?sO%nw(qfJtwRzx3N4sBJd{lW_*db9ivt=vY|0Z!)#a;eBsSR=uu3h%!%JEL4
ziT^WyxRpg0LME#?&3xtU)3B!qJ6&XfO~QVQz~>CK!Y_mPPgf9icrdx_SWa+l$B&Wi
z*6kt6&o#;)E{)iUv=(&0rqv}^Rru?31DQCPx~~WAleIy%RX?FH^x)RwD``cdf;kOK
zOj4!pZ7*b?<vE+=rMClC3r}GoHs_6sJW66SFm@t!m4}0mYB)g~E<M0DZvVi|<lSDM
z<f=8*_80d({yO2>p(B7WUSTh-5Opu(PWB4iw_*lbj-yGQ&iLHiGxR}xA))Zbf@u|$
zmE3`gD#q(KgA=YK-qMU&Q!&7GcW4z6eBu2&mH3ZVert}TVzOnbRYf(kU7{p!SNO<f
zZSQ5E7jZPn(;1(s!cWmBDdzqbTE<8X!Qznmmqlv5ds~hQ4DD2@BX?ADBRPy?9M=)*
zP<rlq8Wnldr3Cf}7j!ksQTlVOMwMt{5|)9M<!F+pGd@<6FB*4yWu+Um!~2$56ZXhN
zKr)`L+pQS3QcHO0^pYrUCi#vPJkKq?9m~)Yce)%e{xNB+P)la4ww3;S7+SH&K<s}2
D=yu?q
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..c5e90c7000
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-revoked.crt__server-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-revoked.pfx b/src/test/ssl/ssl/nss/server-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a8f9b0b15ba6a5006772309d60546ffcea721ac9
GIT binary patch
literal 3181
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
TbYKJshg&M|@u8^*0s;sCT(as-
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..7f677ebc3519f47fb1af0393737e3c7751d568a2
GIT binary patch
literal 36864
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
EUr6s&82|tP
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..66d43f59a56e3ac78870738b91363408252785e9
GIT binary patch
literal 45056
zcmeI5c|25Y|Ho&{G}f8PzB4l-OPCq6&}K^uvU5wsjD0VI?2Lq{R8%6Ql1P$DqND}4
zJ+j@ELQ$!-sVM0;Gq~N|-OtRtUccv$-}C&=F>~g7uFrLS&*yqy<C;0=yg0Vj=Dwkx
zD6im<KuRb|2ecFf27?SxC=duF#CQrao(l_%5kMC<7_Z>J#Q!Q30%@&!2S-kU_yksi
z1SgPp1WN=df{FqU1y&1`{;NhnIY0m)01yBO00aO600Dr&|4jk`yu6a~@?g3~D1{o}
z8R!{GSw{(_EWGj=+n5;InV{?pjm%9@3#BMkUk{X(&8E$UHd|0;CR<SU7MrZ?O;DB=
zC}T^D_2!$5?NBx*R_2DrCM!^kZ>E8`xfi^eHXlsVSRM@W4f61epzR1?h^~~d&|ucK
z>q354+(N`}1cOORqQP5P72N$Pz8*m|npKEzI3?7Rci!A4i*+WBD3-{yuvh`5O2J|A
zY6eSKIeug12%u0s0~V{me#*fTf61YvwjK(TltqJG=WK^j{XE@67jrE6DF<y%{v`_z
zi&Ha(z$B#?dgDu@g?WZ7rh)#HW={Pji!M$r#RkkPiAIC>2eXE3F~MTQGoNa1*cQvV
z88|~DOB=ftC<>PGWQ-dH&v@!EV^#sP1jj7WWp3&+H+5N?%qqGB=8DK%k(etobEV5%
z=`mLn=8DQ#>9JB_=hm!@SY{3!c5Xk+EI2GP4Gzo9gTpct;jqk1I4m<24$I7i!!ncM
zu<p$DyuCSptjzein3WmN%8X}a#<McxS()*y%y?F2JS(#ftE~>Jtq!ZL4y&yWt1XG8
zl2|H<rIJ`GX-;KjCb2S;Sv6Uda4bxCR)6tWW?MX~zj&5s9agyxt6YawPGFS~SS5t{
z66RhASms^`EHVTZ83HRmft8=Y8V4e)Es@oh$ZAVuQDx-BV(~cU&gVm>dtGKJmQ~r^
zlc+<`Au=n>hs+A|A#?BZA=AQq$n5QW$n5fb$Rs)+GO^BuEI#w$Vlx)0`O?K^q`x#{
z4#`{v@}dXwf(O!~Uq-m#mk}=1WrTmJyRc(M>4INIxUgeJxUgeJxUgeJ$b73YJupJ%
z`)xjCzUk&ekHye)G4%Q~WW8zT>QWc&P?>f-h!m^`6^m8V;eknRK>w?Waq&0U6}K2e
z7@sJ7%uf|K;|Yejffx^90SEvD00IC3fWW_vKqlk(fCnmuKy3VbN--+}0>t=1AV465
zh)9wUNF-S%NowI|p*llnR{LKpNkYrSYtk7BAYd?!7NAY@3<>uP(WLnXc?WoEQUXFX
zgBS-bTJ9mC%$;CSI0dX63}O!z6cV=zriFTkc-mT<qe4Atp(t7y<EZ1HT*VFlZ}qkN
z^J-QZJD=9r?X+|Z%v?Z4AScG7Ax%!xvpjVbq%HrQX1h*2KWrHoKO((hA68E~H0ike
z=*pO|c*#>9!|6RCPvWy<+7u5NM!n41e*6y9zrhjaerfu%Nt;Wo)4|n}RtF!=n2J`N
z?D$sPq^+tW`CDjursb*_|4e70t5pM9QW=4MzK4BMTuIHXZHj}vLv1TG8+Oik+pF)t
zR}A^=v3wfoG~C>Tn_l^@`9`k#l*6;5J7q3AZMU%!|Aa3SyeTnLzHz$v<C$C)({FsL
zOO7D&hu+$V@Bgq(>4a8#Kp`TYTAit-_SPDgh+THkZ%^z**-j-NvC}F+=@(~*R>f)`
zbmnVCA6?h;#5*7Zk#y8Pw<G_;2@Nm^j7<dZ!Un?l)sr_Se}RLSz_D&v!^KS@ECGtj
zD76RSe@txDe2P}P+Bw}WlOWlx*HT*OVs}3YtMs~r2Mp!`qp$)nzPT(s{8(8SzgzM~
zuqOlxR)-=CHXFhAd)40w#t$e{`k{&7rH^ffpjY>5B~{Bz_vhtbsSrT&UN!0}-l6AB
zQ^<}OKX+}XiSh?~*)CrBl*h(vidQ)LV7~9^=sx}YS^~VhQo!LjHsD?4i;t%zN6mUP
zF&l?kv1rZNkBuwqQl2F$#fq5hD{Ucf4BPcP?;F>f)rz1C25l4GshbO$eNw;duwHu^
zq5Ij|G+H1<b!vZ9NR<1&X=xEr1)HSorm?4EPh{K-#+M)dEhz;&buHNWV~cM2huImK
zqvdvH{_bD!uv)<l;Q>mz5A&~|fLJN|+i!IM1?{W+ajG}sQZ?KvxMtM|??xZA3aztY
zyJ@^-hNEZoCdajkm1Ht1L(cFNB)zhqdWqh4<+tuF;Rz+DAHGv`@&T`83<D3RVQ{+{
z<kg<&cYKiOTIfPwmsMBdrY7NZ@P+NPZk~=E6~=KGQYOwwjc6gnoCUW@_9sK>2L{Dk
zNZtG1kA|PM5BQ$F?ffyM@PW`q;YgE)@=YnKz7DwrHLaTKmRb*MS`veaBpbe@FIPS;
z8ybkta1>D7JiaAa*cpAbQx~H+p1fPIH)fi$u`xOC(4PGLW19(SRm-pENVI0U@A2MK
zy~(MWDE0ulS!DXF;wj~exE~YGy%L}8GW9mn78^YJdjcss^yc;s2W}|rY90^7Z%mZb
zc1G(?KFQmnQ9DV2Sg7!-q#t{fIpuSkBZc#Sge<S78K+##%T)OEj6nhC=AigC^_x=I
z1Lcr*%+ZFv(#!5Qsv3}`T~jx`m+6%a`OlngCBRN<@T@%7a=wsnaI5ud%^Z#FHLG@M
zw9ah0*E~6K<&6H<wa6W&?J373loK^}O4TQ{;!%+_&yqN`;KKKJ*En5L{i8##BppMr
zix)owSsQfEo%HIS^Apc~LO*t=zbR05e41j^BmkE(gcNZ@0dd&ng<nZ2PI5Oo+vghc
z5Og+GP9$;aSzmC(%>znWA2?FD(op|ogY>j*?7iHivQ7qtO99^@X_;2ba(Kg=w1qe5
z49Jdzt(7k5FRdZWJheUdT*ect`!+v(O($X(F)akYDx+^$@D<;&>`py>DEj<1M|ood
zQ4}<{>(HYFthFFrpO6(=VdUrLU*wv5=kl#{eMi<GNTJJsE<awY>0t6Bn{S!Vw`ANX
zg8W)JJS)xN(b-*U-ya#q#}AS{-qlz36iIPIfz)*W)w%akhj(v1aMCpSPPl|pfkqkM
zSLX@*j@;k6;$%5eP-s(k>Lof2m!WzMK5k=Bm>98Z6lwH(VUaVWJg$D?nPsux7XEhw
z-k#>x>vdMcEfZ<md5E=><ZIPe>b_U2$FHqU+jHo)!N988#F!vW`5(u31?nfYUcL&6
zeDOA^P)xYQF?q;z^<C}Jstp}6>38$Ijt(l=?!L+!cz3v=;WB-?5;N+SxOCnAtu`U*
zTV)J*hBm}X<>eJFKlmMA@TqW(GdC0>_CI;%IBH_NB<$7Krt-<Ou_u_E@fXKUNuW$W
z^0#$494T~Nycjm1kI@>jGpWlz%%DIgl~vg`-u_f#uKxQK;UoFviu`Yt*;^V-57jrj
zEs--beK<@EDb#-h@-65&up!zgbZ6!s@HVUy%Is!rN?((+PKN0za8pFF+HL)$Du+^M
zzmgZ-?(3i5m3VYDLcBz6pU+tTtmW}<Tcaq~dQ=~NxO%N|S>IEY@C~|Ug=?RGcyilX
zwUTG~y5eM?DrqI-ZLrJC>(}7_?;q9zObCqY^Na_u00aO600DpiKmZ^B5C8}O1ONg6
z0e}EN03h&RMt~Or<!4?NnLGZU0U>Ap%Q^^j2Ot0t00;mC00IC3fB--MAOH{m2mk~C
z0ssLP0SE-dKX?590EB!1KmiB<1ONg60e}EN03ZMm00;mC00IC3fB-<?KaYSi#EPHu
zA2ZG!|33yHAOGj|5O53-00;mC00IC3fB--MAOH{m2mk~C0ssMk0FwY3vXP(tzdV{d
z{(k{Nz5s{;1ONg60e}EN03ZMm00;mC00IC3fB--MAn>0~KoUaW|EGUkyoAv)F!CF-
zdxCQ>|LHvi`~w640ssMk06+jB01yBO00aO600DpiKmZ`{GXVi9=Jo&Of>aQ461fs7
zi>yLMAbXKVky{WHgb`v8QGl31)FBe!sc>He6yAtXf{((Bg)RskfNKc7ft$jGggON2
zf>gme!4pCxAt}MHf{%nkgzSFa3$O(c00;mC00IC3fB--MAOH~f|3g3lVhE=Bdb_%N
zhJ?D}2{<B^qU-KDck^^SUX87cRh5V6vs(LmMlLj_{@GZWor+z?P9-U^Q;8UMDgn(-
z)j_dSbrspEWQD)uAQ0Rso(y)Zd#LNlvz2w_*vV9)EIXM<m0>4S3DWFjB1MXwOu|b3
zUDm;R5f(cp!B*B4XD3sMV(erhRg|4fC5W(-i4<XWG6}o%?=sFqkEr+Oh#}d^x(IeM
zl?Z1i6RAS%WGX?BolK+%u#-tx{=dsuDuF=$vtxX0WnEr&GL;BpCljel*vV7^l$}hZ
zK-kG7ERP^Wo_{-qM%x)2;sIwRN$AfzMW5&{AD6}l^=C>SNsD}d!s=NscQ<UJ1i
z0<&3iehDAc9a0C8<_QINg4G!f{+$aQBeldZQ3UkYY4WFEd<vGHfOTHHfiMDV`OE!;
z(PC!jy}yr11!*3SU6W9;^jWG`*+4JtN&MJQ=e>t%A^&j0%D>!O7>a5?<6q!N7Z-KC
zxyL-`Mvscvc8Jr7!g%Kht$nVkyqD7G7)iD!NmwpSB3|xWeLKNrh_Bctt{$n_l(oZI
z#D3Q7g4Yd_Qhc7U4kwd-Ee?n3^6O-W5xL4Py*{-oUmbf|@|#kSm-4P4z9NNgI!59b
zlRwWiT$p?exsVZbD%Om8&eWc7^M1SW$vZ2r=265xbhUNjgLv0+GMV+HOC<8X|4#Au
z_u(>4D$o$|?wz$ut@>+sW$T0PW^ATo#MztF<-(-V&{jx<+il;#H~IJE^HA5_#mtZ=
zAExECy0qXTKO8jVWb%{svp|34nD*f#^5iZN!h=&n3YnPTcd7j`7hE5_S2{$;h_N+E
zC30cXc1CN3)XBUwYl)6CawM8=Wck~Cc+a~7&V&5-(~Nii(+zchdQVoJY82VjY4$r`
zXG7$z17&+_FovbhYMYkduRp9j^t6yp$B42uNu+XN(rjHbx!%-m>S#@obY)N5_@})7
zicNP8zeppj&c-xUo#jLlbw>M-H!g<i!n>l>qI+Z<?Us?p1f=SkXv)rfm@uyrI!1)8
zNh*O0lS=2;)Y1m3FT0tq>!3-zKUkHpzUaot1E}f>Skh#=`g=|$A4oK2xwn{odp3D!
z%^T}G{YOJ>15UX7KndP=8<*^-rPDFOY)ulmnSxT!Ud6OnL}IR!O*Lb|{1$zBu@?pQ
z+PywGu+44_u2-FtNwPzDLDJOg@t#2A0*t?F>~o7a`WcW*Vz81$X=3?lWjbalTa#Q(
zK?5(xn~h4;YOsnzj#BsAH|uv=$~0MtDjXd7E`GhD-iMRPZ%N^;*J2u|TL*4Vobhw^
zc(TGleC9}u<q?tLtFRkx=5!457n47KC*WeZC##0^L|ck9nnz>36!{bQ-hbX^S9UIR
zsTcJ8&aoHw?Kzn&gBL}2jmzpijI-NccIiW%_Mk!Rtsu`m3Z;?bG0EmWbPR&MNp7Z~
zPa)s4-!(>DREg-I4Q)GS88sHxRV{Mjo`ey;I^)qMOHL-=YkacjO`qC0QdVcT6Psmb
z=R9<0vV@FJw@mDr+6AenW8iE}ay12IqDJKTVxF)2Ld~w(wl5(|BJ>in^7$nH@yDp@
zZtT+QoJ<y4H+dU$j7)u)Q9u0YmZ3N?%uS){^TE5Xr-yg~y`lu@7$LSMxtf9sRD^Gj
zJPwe)pgLU~*ViK*G!wt{DNo>CMKOW3D*VwLKfAZgQbO!4S{-(jP~PuOURSj?n?Q9q
zs`>Uy>fWw0Qt2`}Mv$#ZuBM=@{x>tXEyYc&>pRr?-u!Xu;<|P#e_4^<i()x3Ql<J5
zoJeLhijz+~EudX2I=>w01XE3_-THYzczZ$8>%F%IuBpK27y-5>xtf9mqf@de26y0&
zYsJ?4>TRz}|6>#HaD$(D%;pe_LgT4&PA0w1>b-Ocy{;(Nwu$(xCM?eET+x+c=WC^y
zX*IiwGs}bM7=E@UxtfCT2-or`@zK5`=<LjhO3PQLBu>A%_c2<@0(qp~xASQwCzH3w
zD?SI;JiqoTb>L15N=jumZ+%2y;@RJ!ohsd8E4nS{7`|Uj{`~Eoizz5LU90)BOV0C@
zOa+YR3WeuB;Y2ZYOnjX1DPm5}AiSHC$+-CMzgv(R^;YaW8J{b6C|sju+1I$YsM60(
zlJc<hPy`*r%ibh6Q&6wd$@4wgnZ?;_!|ik=6%1{62*pp?-hh`TxngUYE_8A-iF!H~
zT(H3p)o@u)q$yl-u(kd+QY_BRMn2}8q?%u(I2{9HYm%!e=;WS+kfT$NM-4{TJpTIl
z!Y2VOyxHJBvj*R{7Lm(G)NgPy`Bls8(6_qUsHiYEkF3X(%fp4`dyfg{k<@LvnxXM>
z(saxcwkElnf?i`cTRhN*(wlr@&;RCBP!tzZt}!*Cc_pzRB=?8F#~Ds0mwky@ottpt
zn&rz!hcYz#$nLuMZi^cD`i765DaFg9Z_+VPwkElnf_R3HrmDQ;wS^@_?s?v-u@aqr
zWO7Gx1>Ih<d^cWreIzH7?lrLSRrozVIbU9dX(+F4jVA6*TlvA*L54qBb91ZqDLMwi
z)+AR`kmK_G<gm>VW1m!;5Ldyq<y!@`MT=s_pmj28E~9N|GfpP6Jdx2#@tUtOrpM%N
zj^2wAc<MmOh<T)>TcI~D9nWJ*$MCQ<$<-88qO4(bA)_#|RT{0GUtv8SPQ2StSv4Ej
z=;~hdu`V>4lgSq%9npiQl$HIxmKwm6Q<fin8gc9quSt|gF^Kf#m{}%W8N5WEkSMlr
zoQ>=~@*HuDq$#sOk62`oQ1GVdZa<bz#|ADQ)WWfDzZ}#aP;W1{&W}74SEM7Hkz(_^
zezJaOVW@tjLtS&)))VzF|G}m#jNdJJBiIuH1*=057UyLkvfex;rs9+PEJ~qEop)TC
z8dCIeSF*udj)ZF}FpvL{Fc%PVE20V^3=a@$5>gV37kDT@=FjAN%V)-0%*(@&fdwD{
z5C8}O1ONiu6QE<%e)**S`SKSR-@~&Gt+7|E*HjhV7j>oh^}EvMi1n&P;aYCwE~i(i
z*F_=!@DcdaIkai%@W;n0VFfueR%@n$$1<b(pUR=T@B6mId8@g!iI&nas_ad2b6!_D
z^X5SA?OKP!kNS|E2UXI|p$`wPjN4Gxuv$^v&tJuYlgU_@Eyiuq8|1#8?g;MlXyn<V
zcdS3?q8arqxi9gZeL0nmR$ih%e}?=YF9mV|^8We8clQO)oz$nPBwN4Kw5`70uk7IP
z#OrgQmel&0C_hdf{kyY0K49_@4=3W=x|(d{3<Nt$!(PG_v_JUgpG>&FnvPz^)+09@
zFRqfQ|D2Oi%ERBP^EEpCtqUO=Tv<_nYsg|ad+eacD^4C2`L>XxJ3?li<oqu3$`%HE
zzMxAMGSsX~E_1YbeZ5MSj#gspk;DZ@BqHaw?d;*ghZFfyqJ1+LA3a)K*k_{j6+1S2
zL;v%HGbfMQqD!`VKbC%{L9gC88?gSwWb_u@bW@N;UzWAQw}~^C=x7XEkKCMi4(hyE
zm_zQ3?0==zezW=f>0yd+0&R@XWW&AG?dI{Qc1|9@CVpDgEYPB;EZWw7t>%u1Yj)Rm
z_|X)kyGZ)2Qz+YbIvUN^BR8jXOZt^RXWA+iYnzWbChvGYutKW9tN%0K#K3Kmk41Xc
zdQKi+tB!=~8X1U#YjX+*oppC-H`bt$*MdiR`oCC}wj8I?(I~bab+{O7w^-db=FQl2
zsb{og)Gq55?S^>cYQpx^j@MMtjkhNzIe84VwSDA1-2STZdVf;Uijlx0JKqlb22hNz
z;m*wJzi5}FqZQeD<mMcJ=7u|R675DNXFa2yZNrVszFf2Y^-!PW`fy32=$99@96c&@
z-m`gtUAz3%kM2=?fB#7L7CW_|Cu3chfZV;xR-bWnv;td?WG*;X)P6<uSdd+eBv8it
z4x7iForu_%h8w!D?Yxl7H@NLaP8`3U=n^`ft^I!BVb<n|^4`k*N;L*JKLlOpgKR&j
z_8jACjr=c@_)k|5^|&xOoY5Sl<Vy;*tuQ_AS4z8ePx-9Xi<{nUx30?Ewz#&1<0L+i
z=VYT?bN);3VA{<cdWhZF=m&}y)M6_GEefh`7;b^nF>>rpax>Z`L%}BLc%@vUbp3)s
z`@;#mxwj@C#&!KxQrRCm3#-@XL=p;78OLoFlC2d~g$;Bg>_x8hO<y2ceJX746<K-W
zGA|t?%hn`UXMEmxXvahABA+dLQFb8fhVd7rx+6#XR;(I}A;0^;Yv*u+lgaOElyXi-
zm*M)!qZN6(DqLz}qne!pHf{Kp$@6lT=obe%Mux3PuFm-E3-h<`tO-Q6mzen9C-w}U
zc9x$_FT$-FC?B=;Fx@fD$)pmcPdiP2wl){7pA$pf9VKWxbYS(3553DuBv+5dxi--;
z(ris~b;jrZaJ(y`t9WZ}%c`|5(|#-JC?0y-WY2ity79T;ImnyisnQvJRMU<lslCh5
cZF`qqclq`@aI`0md`akwmR15b@^`WS0akUV5C8xG
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
new file mode 100644
index 0000000000..82255d3f4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.pfx b/src/test/ssl/ssl/nss/server-single-alt-name.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..95616f580e4361c493962ca84bee51baacaf7dbb
GIT binary patch
literal 3213
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server.crl b/src/test/ssl/ssl/nss/server.crl
new file mode 100644
index 0000000000000000000000000000000000000000..769196dda6dfffeb4141ec007d6e276d3afb5e84
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sVYt_DoZU=NKP#(DHi87v@kR@GB-3fF)%fa
z66ZBBGcYkUfpQH*41|~%+0YcBIfJ>0k&z)cx-<6WM&pI2r!*dm3egYCR+DZEcFbs=
zJLCSW&CW;U#5aBOwXHll$=p%k!bPiXhut}MT~u<8-g_X&?EdN?ab}a1JABr;+ig`=
z<67*aH7|L#+SOR4x3|MWJEoZ4vY)6nxx9MOgT+t(bDezlrzuZUzqqQ}z2kL-Q>O0U
zHbE<wjSIh&+P2+I(flFf&)N8NZ@bU8AI59GGNp869L-9#II(HANW;al9|>u9KQFgj
zG1J5K+BS|et`!}!yF)I0+pZ_l{^ZHDOBr?gx7Y$1Zp_zSS614#>}BGLu8NhBoGu3M
o9tXbUKYKSkd*T+M_r-Y*A{D<U7Q|28yuIn;%#d&L*G&^z0V1ELsQ>@~
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..93d58e2651fdf3614ff265dba5629dbb27e6887a
GIT binary patch
literal 28672
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
EA6+1O;Q#;t
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..7493954711430fd0967475631b9380d8399e9ce2
GIT binary patch
literal 36864
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
aNT9M!FAY>S@WFw~MiitB)`a;#m-rj=KB=Yv
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..b81ced09e6
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..00530d98af 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+}
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..679969902e 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,19 +24,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -145,12 +165,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,26 +185,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.25.4
On 8/3/20 12:46 PM, Andrew Dunstan wrote:
On 7/31/20 4:44 PM, Andrew Dunstan wrote:
On 7/15/20 6:18 PM, Daniel Gustafsson wrote:
On 15 Jul 2020, at 20:35, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
if anyone has an interest in NSS, then I would love to hear feedback on how it
works (and doesn't work).I'll be happy to help, particularly with Windows support and with some
of the callback stuff I've had a hand in.That would be fantastic, thanks! The password callback handling is still a
TODO so feel free to take a stab at that since you have a lot of context on
there.For Windows, I've include USE_NSS in Solution.pm as Thomas pointed out in this
thread, but that was done blind as I've done no testing on Windows yet.OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).In addition to some work that was missing in src/tools/msvc, I had to
make a few adjustments, including:* strtok_r() isn't available on Windows. We don't use it elsewhere in
the postgres code, and it seemed unnecessary to have reentrant calls
here, so I just replaced it with equivalent strtok() calls.
* We were missing an NSS implementation of
pgtls_verify_peer_name_matches_certificate_guts(). I supplied a
dummy that's enough to get it building cleanly, but that needs to be
filled in properly.There is still plenty of work to go, but this seemed a sufficient
milestone to report progress on.OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)
rebased on current master.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-WIP-Support-libnss-for-as-TLS-backend-v8.patchtext/x-patch; charset=UTF-8; name=0001-WIP-Support-libnss-for-as-TLS-backend-v8.patchDownload
From eadce6d33f6406798494a35f4997f49ace5e4cbb Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Mon, 3 Aug 2020 12:32:10 -0400
Subject: [PATCH] WIP Support libnss for as TLS backend v8
---
configure | 211 ++++
configure.ac | 30 +
contrib/Makefile | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1032 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 975 ++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-encrypted-pem.pfx | Bin 0 -> 3149 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-revoked.pfx | Bin 0 -> 3149 bytes
src/test/ssl/ssl/nss/client.crl | Bin 0 -> 418 bytes
...ient.crt__client-encrypted-pem.key.db.pass | 1 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../nss/client.crt__client.key.db/cert9.db | Bin 0 -> 36864 bytes
.../ssl/nss/client.crt__client.key.db/key4.db | Bin 0 -> 45056 bytes
.../nss/client.crt__client.key.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client.pfx | Bin 0 -> 3149 bytes
.../ssl/ssl/nss/client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/client_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root+client.crl | Bin 0 -> 393 bytes
.../ssl/nss/root+client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+client_ca.crt.db/pkcs11.txt | 5 +
.../ssl/nss/root+server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+server_ca.crt.db/pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../root+server_ca.crt__server.crl.db/key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root.crl | Bin 0 -> 393 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-cn-and-alt-names.pfx | Bin 0 -> 3349 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-cn-only.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-multiple-alt-names.pfx | Bin 0 -> 3325 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-no-names.pfx | Bin 0 -> 3109 bytes
src/test/ssl/ssl/nss/server-password.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-revoked.pfx | Bin 0 -> 3181 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-single-alt-name.pfx | Bin 0 -> 3213 bytes
src/test/ssl/ssl/nss/server.crl | Bin 0 -> 418 bytes
.../ssl/ssl/nss/server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/server_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/t/001_ssltests.pl | 289 ++---
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
101 files changed, 3276 insertions(+), 257 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-encrypted-pem.pfx
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/client.crl
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.pfx
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+client.crl
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root.crl
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.pfx
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-no-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-password.pfx
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.pfx
create mode 100644 src/test/ssl/ssl/nss/server.crl
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550b92..961cb56358 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..00ae054920
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..6401949136
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,975 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2e541cdfc9c6da2ce489ad74bc2f32e25028361d
GIT binary patch
literal 36864
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
T<IT@{@aaNdObzt5rU3sJIhEY#
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a6cb5d9248a87e322d39172bd495eb428b2a035
GIT binary patch
literal 45056
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
KtL=5`OaB6Y_1OIY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..dfffe92b90
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client+client_ca.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-encrypted-pem.pfx b/src/test/ssl/ssl/nss/client-encrypted-pem.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..940af9f2967aeb1d3be1c41db6cb2d1e21c757ba
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..78bd1528d61535531d652ea1fae1e1a0b749d696
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5eff0aae8ca3c4fa615a1ae29580f9cc407581b9
GIT binary patch
literal 45056
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
VuUpY+#=ZPFXt>|`T2$G?e*uT81eX8+
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..67ce598fd3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client-revoked.crt__client-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-revoked.pfx b/src/test/ssl/ssl/nss/client-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..2ab711880535eb3cec99c033365797487765f042
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crl b/src/test/ssl/ssl/nss/client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..ec061fd17bd11aad4eea2cb4980cdd9620be5f9e
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sY=etOw9vQsYNBl;=G0yhK5GwhQ=larlwKi
zye4J_CWa<Zu7QYw5ECO4nj$o3FgGzWGQ=5uWWFNvib1cOm7QIfWA#LK*3-R$b#Zr+
zQzt9Of9Pf2QgXg#^7|Q#`?^;~od5q&E_3mbwX*KnbDH;uHx}8yc;&n*Cg`u>*0_IV
z2Y54&+qGrPUC4dt$>ZO*)%q{4x14vAo8!GyW3<{9)zs`N>D*O<h4!Wm&zYD%)*XAW
z?8h?G^mEIM=O3^Yk(m1JZpp$KRu`9gue$prUuR!%Rb1VVhy91s9M8Qw^)xx^n<TUM
zv3Uho7B_OK8=h5cdh)~l<(Z{@Hw(mAHc$Slv&lrgXD46g|EyT1`^j4q@6F3L%d)b)
nrK%&VUs_%AV!{6+)9&7a`}PORtmIRl{k+g!;Q6Fz-w|N|QYfe5
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
new file mode 100644
index 0000000000..3ac5759692
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
@@ -0,0 +1 @@
+dUmmyP^#+
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a50e21d0e379f2b24547cf52127b06329c6bd67
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..3884ab59f5568f08e5c65bd753fe7a24c4bb2ae7
GIT binary patch
literal 45056
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Ðea6+}HexqpgK%){B^(j^1Y3gf
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
OLx$%;!>EIuY5xMt<fVxK
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
new file mode 100644
index 0000000000..190f880c0a
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client-encrypted-pem.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..96ef52b347114a4403c97567215841487f8d9d29
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..bd26c68db1e1d12b48ae7b38be0a2df3661cc094
GIT binary patch
literal 45056
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
MpZrikc6yKh3rY97u>b%7
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..3f0a9ff5b5
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.pfx b/src/test/ssl/ssl/nss/client.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..ba13f0cafff3a3d64ab5c1b26e98033614e71fcb
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..e0a8a94d56e8c8fae91d40d6c67ba5778f0e122a
GIT binary patch
literal 28672
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
HHvs$=4>)Rp
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..5793b879513b18bb5467717532e575556422341a
GIT binary patch
literal 36864
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
UDjRv3ud)eA{biwk-p?ie0)~o{+yDRo
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..212e72edeb
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+client.crl b/src/test/ssl/ssl/nss/root+client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..54a75ba497fed1145ef8b0987417e5de6449496a
GIT binary patch
literal 28672
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
N0Ryi@{@4QmzXywqjwJv9
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..55494e66c0b0aae452fa1e58ff6daa0ee72341f1
GIT binary patch
literal 36864
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
Yl|@byuFMPSaAhlsh*2j+hd!713q1s>zyJUM
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..bdbedc732b
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..faf4480a51f08ed862464fc42753d1002c886912
GIT binary patch
literal 28672
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
H4*>if6nm11
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..391fefa234b49726959a94205089a04167c44fb7
GIT binary patch
literal 36864
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
YD|-ZWys}AD#VebUGD(%082en}FC><sZvX%Q
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..5d04ea23ce
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1bef7b32e1a48253dcded78d36eb0a22062511fb
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..633f26005c9734b998e0ec8ec699cc1c0164be48
GIT binary patch
literal 36864
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
YvV)U{D;s!OxUvyRqhv|y(B~3=1DlPWL;wH)
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..9e34f7d8ea
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__root+server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6dcb06f709163abaa8ff8779475330009cb10925
GIT binary patch
literal 28672
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
I=mmiP1_=>&y8r+H
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..572042e848fb394849dfc9a4aae5f811603ca074
GIT binary patch
literal 36864
zcmeI5du&rx9LIatuIpo62M!9(y1Pyu#mUZnwH-pFj1CH;doW}J+;+QlP}!JU%OgYx
zg^5IsOaqRoXh2ax!c!g+U?egEA|eQziaa#n3lxL+Kt;XhwDm%eZi)Zs@@vx5-~Ij0
z`JV6joZGwIE?LJH=J|s@y1K5RRt(adB3VIEis3Y^P$-nrQ!72A549Aiq6g9|bwhl+
zP^lPv|Mv#tFA9xrs6zjp@g4nIy{J#qoze}}t-IYI$O8c&00e*l5C8%|00{gy3DhJc
zn9OD>bZ=1f*7$0DL2;xQ6r-=2oFbRA*hLpRN94KaXqF!6uc8Zz+~b@@rSw==DLujC
zE}Y<^^F4G<z9%=&ol{H~xeD@}Ij&4v`pvYK3BRz`EREWfW2O}T8CAYHf%+P$ST4>C
z){*1#sD3#UjkF`wYLh9QnoK%WP8a=EGXjBv2LCKE=u3#0b9+X*N@!BV7UT}}K#@VL
zb$C3{m`F5gM6a(#?xMa)gA{Mk;H<eSwJ9~7Di7P9>3!H&8I(2RZ_)^a%eSa7h_U7<
z)uz7D)%XK}nZ5>DMs<@+xO$6<gRwqdL?xKg)2XNH$d<_haugSl3U8a7@5;tGN8}e3
zXVM~)p3=UFtn}n?OcFT5;FJR&I`E-`9O5nx9ybDRY`C%G#(|q`+=#gGN=-JALgAJu
zBCNrn@H|+BL0E=CScgGah(TD1L0F1GSc^eej6s#Sjo1tOBg$+zCdw>PW{EOOlv$$8
z5@nVsvqYIAV>vRGBV#!-mLp?rq-rBo8>!kz)fTQ2WgAhplb(bULol&qy)43GS+ZW1
zc;-l+BYBSGd6MBthL2?MEIh)q@PrIc$nZp;C;B|u2Z4+g$XJ1l6$n*H6CswtlSe}A
z-hs1-bguLX9M1{3LnOo<A|akV5@HLH5U(~8;w48yOf(W=tl^OGiG=bnLMoD#huLlz
zhPNc#!7h8SM?KhNzfu_WD}~X%Qg}n(=)_Vs>Q@S*6H8%qVkwMHEQR<~!ycp%pKp;6
zpLCJ1N)CN;Sp9cMPMUCEuWZMQ?NkXOs`4Ua<>J()QR%n4Fv{=Xaz>8vy}btiR59Qt
z88`3%0zd!=00AHX1b_e#00KY&2mk>f00g=#0Ucf#-2Zpi?S*v#0U!VbfB+Bx0zd!=
z00AHX1b_e#AOzt4A5H`y00e*l5C8%|00;m9AOHk_01yBI-Jby5|9AgAh7|$<AOHk_
z01yBIKmZ5;0U!VbfB+DH`+qnGfB+Bx0zd!=00AHX1b_e#00KY&2y}k}aR1-^_ZU_P
z1b_e#00KY&2mk>f00e*l5C8%|0RR7gjqaqvSdz3gDaqhZ+@F}HpDQKc0R(^m5C8%|
z00;m9AOHmZF9bpsO}ukqPes5#wY<{T5G-eT!Jf_A1?h-<8CNz6EkIM$+Chs)FaFs;
z#V4VB<dhE+5wD{65B%ub#YHdOe{jq2lgC#EesWc(=|3BhrA?j0?%U{|x-gES;wT#F
z)S9qLoc6|8MfVLXHyoKU_{`Fb*4e`r)vs+!Jl1@~zp8At=hB6}XWHk5ED5nqI=WzT
zuRwj)K6UN~Xh(4S`CfOPTQ<?AyLh~1{NbOyYx;RstnbWZX8-!emfaKk4^PP3)Z_AN
zQ&ntMYVGGs-`+o1ZJlL!#t^coW192|T`>90plK%4z>?PMy?Z|Y#-HsU>I1DG@9f{_
z#1yVKTix&6ot>F%PIQ;f&v<m7dE}*hy5h^c!NSG$Yf5&^_%!Xr;wC%yK*$mw)1=_-
zg30zWWHe^Z`|YbFucr2La^cmNi-#TIdau0eA<LAem4DQCX7bP%DaU5+?12heJD$B#
zU*FNAPvh@(<}~Mkr*@s(wdc#ikVO^Kq?hl4$)J*5mDGIj!pM$m>+EemG!1!5Tj*(>
z{-nJgZGKq!`V*a*d`nz7YQ@m@-r4p|UvE2RDKWS0oi_jX=AL`r&1@XzF$@b?lrc?8
zUpd$L8>j8!X?x|R;H5c-#|$ysPc3QesC&<U^uUTc^wzPt6`_pIOfKL2;D)i|veL@V
z9B<bJvo|lfo32Tv_PYMqasKjS&s7IQmbjQEZKw+-%jzGU@Ufwmn|!vi$L^J$7dMP`
zTy?*Bezf6QV7pkg?0RP=pS;iA);FVL$G6umzn;9Yz3gn#tG(v7xIbBTXzLTdz8^4!
z`cd&_eqqWZVjwWPuAyp)v0}{Jl%wOc)&bd##nV<T+HKOJPzcq^R~-YYxaA_W?X{m$
z)~x<KO*>}SuUVz{Y??mntELG<Uzjvx*A>lyS5D9R2b-yCZN=izluxOm2C0&AHy-*r
zLwjXg!h)2Rf)-V>D)+7nKOQ^r{i%6dnhw@{bO8VTUt>&F7_S<K7*mbgjdP4Ajm^f=
z|I4Kwd;$R=00e*l5C8%|00;m9AOHk_01)_>1nyQksoQ^osEJv2B*ZFv1$C^l;Ej(}
X_VTJ&WkFQND%((;UTM}wKbQCmpCXce
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..b54121fc4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root.crl b/src/test/ssl/ssl/nss/root.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..1ea3e8b64630be9ffe7463c1713556679c180e77
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..c834a04c75e060c037de9d211fe5588aa18d7198
GIT binary patch
literal 45056
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
AYXATM
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..bdd10448f3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx b/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..7a6b23bb14fe20ca32e9b1055a9c783e9ef63ca7
GIT binary patch
literal 3349
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..03d88f8838bede656dbb48a5b56f42d64ee38d43
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4c91baa85e018d37071a3566256c88dbf5d53f09
GIT binary patch
literal 45056
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
Ifg7Iv7nl1PP5=M^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1e4d3f5fef
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-cn-only.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..0d2d3e5bd060df6e7b5fe4f574a14ba91389dad9
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..4db1426c3a88f0bd27dd3dd1593038e228c82540
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
new file mode 100644
index 0000000000..142748c1c9
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-password.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.pfx b/src/test/ssl/ssl/nss/server-cn-only.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..052a8847cb8f8feef763a7220540f540d8016f69
GIT binary patch
literal 3197
zcmY+FcQhM{-^NKJgxnI1d0Ww{y<%@_#NPDQ9yP9-MQm*~wQAL>nHoXSqLkRG_6~y9
zti8qF^VfUM?|tw4{_#EEb3V^G&!5i+frbPCNN*v~5CssJCtN-J<PTDE(i}7d0YpRi
zeq$~K8Z`TF1j<2!hJK@d04d4u*YR&cint4+{BHw<6oj}9q?kKD&Xc1s0s#ONBq3-}
z+D9^w9;pvM-gbbWelghQ@BEZfFFNiYVTonS?Ow7O@k*o4MQ~*>%}kGQ=+c8UIY;U3
zo?g4|1=U&BVNEi*;+BLs#9VfIe0`br(O#Q$@R+B(Eza2CUc__~T#U<CYB`j<^c2@4
z*=-(=p`DY64YT(FX5O4)q(^^{Ht9|EyhQ<;dfl4)d7nsrj{V@rctm*cn1@BjF!z4M
zRUo#2HIzP%I5QV^UqPRI+FFHSQ+(;#n<{U_w2*k)aQV^A1Xb$%!$vb(i5`syacI>g
z`)@+-gAqp6YprGEC1xUB)I^m^WUq;4WbMH4fTTk!H0o6OP4QM~eG<z1C>sfmjuVak
z8MHEXkuHuWM?DF76H5Kp{7f1--*@YB%9|+}m2odQZau42mzUhszKt1%r>_3q?e88@
zi==qw=oU{LRKb#*2MU!{x_1Qp%mxGb1X5!6CAyqAaIf-G>&=HM@uDc9m$+wC<)xPV
zp}3+k>BSGgcrlUOphe|QX7Lq7$dRLsjnGYXb#0+a25!-jM@QFH;HAL}GxiHp4x9D<
z@-L$^;HK#z*$_@6d<`Sjq}ho2S__to<x(YC<gF;k{_uHfb3=n@UEVyM&R5|`=c~f?
zxb4H%Gn;0ghtB7sKl;kDrcRz=tl$#a=&f(jnJw$IWCOV(Nj;H@if*iz^RH^rRgwp!
z8Icn*r=QXyC2zo%#vXB|4w)GkQvDASd=W03A$&m!M;ph{Izm}{i)v1s##GJFA#kK~
zJjZF%7mF_A_^Xje?Ahvo7y0sKK)P_Hjm={roo%_gTTn~AM8gh9D*ST$mWoU^l|D<D
z9*f&@YNu8u@xd9%{>1g2`5BVd=*m0kG}`NUFf7Z59{EKp>;COyVZWzF3uSa#V_g^Y
zcoo?&bJ!|xlI`Folzw1<@HfqAY}z=q+v)+I{>zXFI}9C%Wv;}cyH}@bmS>Y)s&T{&
zPoxO?)1<3`juhYC=Rb=rIzR3+$;lcpaQG0C!`^+RqPUpsW^z#iZ+tt3+G|i7!P*o}
z#p7zT%j`Z0NX8+roSCc^E`~mD-C6(c%bQw#s9<FNS~9gPI((IdkU~y<=hygx{EVO5
z>LE7`+7l|c54seej4u**lr$eU8ri_<z$z|O8uw{`*?IF9^dNV!(lt^NhrCiA>XWw4
z#;z7@O|=*xJuS_WI>L7%K<n8gxzo}>+Tx0b$fcoWdTOP)U3WTS_Yp8CG72n_&c(Pr
zsYh1FJ<_)PAh4x&4h5Cx`&_$l#c>*DTgjFpLDu$~wvQDe(SO@7a?Ms%XDh%ZwlmnP
z;u0HnxSdPL+|TX*r5(isOA<RFCwgpmWxfBbkS*g6^S=Sn!#q<QP&-U>r+(aBWBqO^
z)mITFP<#wO23NX{91h0pm!Xs-nid+DO&l})I5`uJ7A0FBw*$x;)e4WKw#{5C3}yn&
z#?J{F*iTK>u7_j4ELD`O#tpm9g{5-UXU2pRc=OUxa}7VjO}C!KOr79Jaszml$R@vL
z!j%k@m-gU=86WT>{!yR#t2hXSgDN7^->rWcDzp<vb=Qr9F=1_;f4DgqVg|x5C=N`H
zeuhLgQ~ho6mBwN`k7B(MG2Dj^4>OR$?h>MqNA*;?M5@u=IW~+GB@;3tRMtEoy6fH#
zlY4{8&t6<!2$D0g=;kzBshW2LM=U2=6Bj!b#D!&CwFHiSdb&5MJ)xV#A+u%M*wg##
z$sxj;4k*`9V>?Ri8l)zlh`q`#PeB@EejZXMjA4Fl(nQNr4U805&-7&`>Qb)`RO0rn
z1<6%<<sw@D3TuC?l-P!}+wt>bXbO(ml_Hir1e2sacK(Y0zRFD5u4|gg@6*_b|Di77
zbYqut$Xry}1s(-aPaOvqoldI3<44!<G3&T_ZSyVCl8JS_xMsFzc`T*DO=GXSy>^ck
zJ(eyuZ#-Ab&iyHv1m~FIDkExG|Kc(iwrXwvV+yI<7O!k@Gi5Ju^QyEw<yfZ~zxVFU
zfaOM)ce%Ir0Ab6RDU1;7qp0Q_dsS<xJ6h{06HMUmJV2?<%<1LQYP(3rYJ5VUvd0zB
z)j0w3|Kyv5T9hze4^uV5%iYR2thcv2AQk6OQPp(7;O-EA3GGeSxD^bKJxpK^cb*F*
z^GBNc78Qk{lYF#`Rm@O#U-Sy>Ub1V;9_>lPc}%_bhF4_TUw&ECH|Xn<dK1Z#UtT$c
zO;(5T!y@Sk=w9SelCOG)a}+tK1%W10{68YgL6Zps(PRR@G0*Sg08#yq)+k8<IcVTE
z0u9{wf3~~zkL_;I{i8neQ)2(v4u}SJCy4nYb2EHi4-WFMDi+*);||Jm8pC@*sP(BS
zx@(UGZP2<LQ~Qv??_K8|#Hy#h$}}HqcC2fT4X@sdzQQ?r?2-*odEe5ATz)wU<}B8V
zk%<93uRJ^0yQUdph5o6r4Rff_nhile9ev;Seqt;H7^6-5x67R^w4j(Qz4H^N!2@P6
zsdBQU_NBN*6wc1>anMnM!L_S7BcLLxE*vnuv(`TMEUUy4=H9dtu1(SN;m7Kag($h)
zxH9FmzTU=3(l*Xj`4hm(852aH9FJJuikfgj2GM86$yFzMrKxRmz}4##oC;qDwK6tV
zP@0)EVmc?^h`9PS+=+d??#<miGU5pQ#HbchMKXKKIA6a(ht<?_d~A(VkH6nT5!I?t
zeKlPJ?=-*O+4{imv#>`LC3yHQAi{XDy|b5BrFsIEf)zZRds7*21>mMN{fXF&o{G)8
z2Gg)V-deRF-pdF!bggK)Tnu~5GPUxDo)HbeJ0iCMoH={aa+MN)T_0eTP_`lP4Ie~n
zu(28wW+-`buGr&f_DSCdEOl<u++;Kv?4*|vNAm=%dA;r)+2}jGJ9Hzf1*r?TM}&1U
zPH3w8SA8p~+&yt#ax_Zk%6LJj+Q%H;%gE`}wB2>b7|Ht!Fs(E{G>+jIUGj0PMAYAt
z=-$wC+>zUT8n|v#(&uQ1&=1Bu#DqgdQNzi<M%<?I*e0#(d7`N5Z>c%v-F?49xT3mC
z%Cz=Qbu$abF9x?Ns7}t)9B7~?w>sxJWCjQ9Ftb+^yKZ-r<yu?uSS;asMe9G)p+|0$
zWJ4`dG<M$hyaFYT628lECn}(4jqB^Zmm?b(DsU!x`9e;Gg__WZIsSzSD;(v_(BwGA
zFavY}JXv4sW_8HWuv+Q4nWFFT>7gI&-njp_I}7j1VuzG`Gt}@!MWU^^*(czU2qmGn
zLDL$u3wv`GRZ0e~`^&OJu%4qekuIq+M;vqb^|_Qr4S(RlWvGlin*(a>=tW1;J56L(
zG~kpb%PeN04oVME*Bm5aK)aMjx+-QpY0>-H+g#La3%9H9pb{}V8wOQZdlbW4*b80`
zs|F&*?t}vcWxG<2I`TDT4)a~(MOYgZ0~qiK4^H<?^$C}+*f_&SZNizV-Uoc%Dxg-j
zl#>7%G$3l9hgnYY*BkK6yJi~NDQpLwUD?-fAcjqoG8aXT5w_k^A&d&phAWqbyBw+$
zE)KcJ!RUMSY)+MK);u_Q1DjA<Fn9B<eTlVM`a6)(PybN?%Pafr<X%+?FbX-F9q`z*
z?75ze5%>Dq)qN%V9-*)q!2X8BDAS-j*;K<xVXk+I2x(@>?oPs~68dag&Skyhv8ca~
zn6I<Tz_q)nZezj35M4zE?WXrx9g+JWfBWg!N2szsyId7~o?k~Ae}9p>UMesg)-gSk
zy!oZbwip6GE8b*MT7~4fuO;o~iwYC=kVKZki=1iT0U_D=lCj4zT>rkb|M25iX9Xpc
zL+xz9Laz7rD&v5}&9&(ve{8C`-}@mVjTG<6z|?^T7sRfw(+2kQ6zR<@A69z1{@`GS
z6{3&~q9}7)#y(uo`4}#i!R2txM`aj8%Em0{bQp_y)3OlHa6QmJogwDdLHV<pMTi~2
z_P^^0rW2wE!UD&3-e~9y9J*Q6=3=+&eqr8N>>-2@_Yq(a8Sm{|0A?~0AS{>?)#S()
fK+p<!9*uI!47T<J@c}8q1m<mmvZG}FS;_wZs?8RU
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..d6aa6d7f651c924dc55f8aed978ce69888b4ac68
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e740fd73e936386e125ee24cca039e79dcc932be
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1d08f78285
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx b/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..8ed37c04dafbbb1e34561794ae0f876e0688c115
GIT binary patch
literal 3325
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
H0s;sC!G=jY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..bb19429e5560cb66b93edd9fbe4f4e5d93e1153d
GIT binary patch
literal 36864
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
V*0@6&e6Pp6#87nXFD)#9{|D0SSLOf!
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..e66a9a26ae087c879a16a4d666c9ac970fe3de48
GIT binary patch
literal 45056
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
RBoyDtmNG0ILbp9Y{tp%$q~QPn
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..78691fd862
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-no-names.crt__server-no-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-no-names.pfx b/src/test/ssl/ssl/nss/server-no-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..c35fcdabc7a2a7859bc541547a557ddb7b5cfee0
GIT binary patch
literal 3109
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-password.pfx b/src/test/ssl/ssl/nss/server-password.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a6ad1bb869b043715aca18b25791da12403a6be8
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3431d1549ef5c2c073e938de6b106f535747b9a7
GIT binary patch
literal 36864
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	f?a?`<~)v^C&ynLEAd?LgIW`
zv^1&Nck9*8*}Q8h8`oXvY$+};))?hP3u`kA%70tFJvGnq=1Fnw_IQQS#qgL5+RkBc
z(Tb*IT|0Zu_#8h|yIG@8`FuQed0v+4`Q$r|jctpq^p&62>Fl5d!@3+j^)08*k@Iuj
zndTIlstB*1+CItQ-S*OD`kLi-L3`?D-z*KZ4xar}++GqHhTy$QC<9f%9@!bDN}-};
z81C-N3_H{qGZ=C~tw~#34OfiZWb&$+;_~45i3hrOf-gS|P7XdjI<)CYL`FhY+uDYl
znoOhRD(*Jyqi;0^#~8OzJNGl+6wYyuNae(6q{d%)Ikjod#YBzT!McT0OOx+EOr2IT
zF>jx1-YTB0&i>Q$_c~v>(D6L(;G6YhcCE^k*QjfnzuNys;l~9Q-@oCkPuFUhcV_G%
zQ;OV)&Qtfamj0_s?t*&wZS_lHV^x!)X^WFSCKSJoGtQna%YVP4V211M)yIvxXDeFe
zoYbEC;Uy>SXVK}jrd@Y$=q~mZ%ll|16a*R$f7@}+h&ie}d(Y%asnhaHS2ZT;-G4`4
z^g!Qh5xNhGYh0+*I-8n6Zon_D{-2S*acTAYRl2XaXIM<4EWsU(3f}ibCE^1&|KRfH
z-HaNx;NlFf5u}U#{!6u&y%!8&&&mw|I2eX8InTZ;bYY%2>fXW4x1TZYm_F6syztSU
z*)0y|bkh<p9G&0jvSP!C)s*9F9LR*X9R+PL!M$x?3W;iey|*>!Qo@K}7tm)Ba?IZ<
zVqWOA#NWd37gt>WdHt?)9e}q1u>t(w?)6{J3x-4nk2|~Rh~cwv1ApPuBUSW^M<Z3y
zwt42w{Me)@>WWv5>L#4cqZBpocMVPPL5f3bZ0zzDPC5~e(5rW~Z_yudbXfe(+C%S5
ztCLH7G-jAT4f3$EsOcQL;Z|B$Ndy0w$5WY-;#!BUoln}9TI>vHR&H+{aYr<H9QoQK
z*Bvh$+fKZBe!1&>b4tmKc_jV^sO6B6ozUDEwiWrzxY233%{YYr!_dPn(*+4B*S6MG
z9X5FHxbIv-omb@+r^x4y(ci4?&Z3x7x;{9)G7wJZE;(=BZZ+h<Sr?`Kr1LFHjQ1Ml
z&Y852D!)wcLFMwinuqW#amUm>yBi{tk`Eo#qxk;`_d3JZz+y|MqouuLSbe9r{lRIM
zj%rL>5mvnB`P3J}8%LD03g=bi9G7poK9#?H@}(yI*oo6DPd%0!@x=Fr%whfRI|=rC
z?@YLK<D|O3T-f6?x)XK;&zZnY`_^$eN0yR#%i?rhb9w$R%5ItgJFe_ol=iCC^ZEO&
zvzU7}u8&Gj8`_w~eX?d`z>8m2ALEaDd2iief7VYrXTE71KK=Q;pYNGiZM<t&ooy+s
z$f!)!oYQ#k%-liMN$;O*GdJbG%RMuTye96Rht4qnhe^Vnc~8Z1LzBbY#HF-`!c*z1
zs^W(XzQFW*kSs2+bPCCMsPG`;@>~tOiji3rN>@&I&apZPO^>7MS9{=YqyJ|;3A>U8
z;Kcsg*I8IhA@%X5YJL3(-ud5K`sq!v-_GjwrUNiU5={HGKh1V?zMbVbWy6@(pRO;{
zxmB;cS$Rqz6y?vj7|p)<hPLDC;-K|wCc=b2<v|-1Fn{_PheXZ4-k)N>9fqOC{pnZl
zD`H<_KVp9AXKZ|5`qTlS_PbtnfS!*2UC)}cvbx>7_+h+3W1A24*wuC``B@8PRJK%T
zZzz!GFRIb3RPlSI+5Y5Z@Y!^YE_=7#3$JFMsa8L(<v2D+v3kjZHi6MEFUCdjEmu{P
zZt!t7x|FhL{r&oDodv@$zdUQW?{?K5qo@hvyNZKzXD0gI-4(0vsj-85CElc}tS(j5
zrjU^y?z!Mb(f9|p)!*w~ZX2_(_`I;_k!NX9Qde%~+Us{p8|7l$Do1Th>fS_sRi!Y;
z?(xw5=cX0hV%S;kE{n1`v?MLL{+nkHM%beT50Z9w=5`!)F0!%I-~1>+wP;i1Vyn9Q
zO_@B`7f(^OM)|s}`Q4Z5XdjPFvl!$RkG$UhVnTV&Fv7Ebae#@hsVc7z-2B+#v=Y+G
zOi8PLwq(oMXNqTP77z0<jf@I9U9nWTeok88v)y;r54*nQm}teGYq|T8(l^R1-EwAX
z_S&C@7i?}Sxvm#->o%`mUnVU_V4U?f&LP*csc>W2q=IMD+DFZ97Oi>jIOkj=MJ=pu
ztMBEBjFdHF+**QOkI7%=O<pRh)t`O$k>(Wg>!mGo7w5{HwD4WNapd(*A?;1u_0;P&
z3)faxblfdH?CZQ=t$S&P;nL~096_dGnakPPw9%e#Bjk-cZkpCy+EuojVL%OQUva4|
zNcZ@6?~7f>8))wGt6#q|r*oM+xr!D$*9NlAVf|8MrD|d{Q#HNluvOwZm+T*LH#86~
z5Wju~=w$xd^dH9O{}h==5Ti*ho4yk>zyb&W0)PM@00;mA{|o{v&FH;TroKZ`Q{h4k
z>>m~?!opK3s}!Iwr&B~;-ztCF#(%9Ct3)CKP<U-D4)xWqB3NVz&%^%zuYaq?g250L
zHI(f=XrIT+{>U-t=R6ntW$E5|Zok8#*v*Vz&vHANeu&|0o80c#DP$wwn2!%r<HzZk
z6&@P)L+G%e^nI%Xg-e#Nagh02qlQN5PcYcniTT-+xL-sKNncEW`X4{}Vt#A>`7Igq
zng8FJMc)^le|K(M_KQ~gNyYetf)TZQPaz?dmm|Kt{ZsIIYhkt)k|Er>u6=Z%Z}d~8
z;qfJYsj~Ht1;#wXTEiLPv8J1UoYYL;(t4|jdPC3QQF*IgP{hi0I)%rQ?nQ{Y!Vund
zjoO=;a)a{q=-<37nK`(qOC~&L|Lc-*uCA|-b|3V&o5&>F|Lf|k$J9JXS!Q@;p09Rj
zP0YS)s(#P6sl@uvs@XPS!O8LamMnSUZq2KQyEjp?hC`G5+<AU;Y%+^I@<`iSl;%g4
zFUX7?-cdJ>+U>e4?Mif9aQQajtQ`$4@#U^smb2gK1mtPvK2_grnk~rI8e$k0{MtJu
z;M)TfJ=-{O8Q)TGq<rU}h#G1YC%4ak-*DodAJ@$~>^W(k{tB|GPq1^u{noWvsr>y0
zA9j?FSy}o+d40URl|c=8GV|aFNS$Q&R9E0~IpY2H+dpS6$@w{VMZRL5Q&J6Yjbf8|
z`iqS!yxkX{$xb=&eTLoQCK-d>ssYxJfTzn(7DpDP-8mH3=w7F)qDRUoiO<-jkBa4L
zTay9?+aJ2(@*$p9vWcV<THs*+E_CLmm8DMZS7XA>{qG*k6eMdqw=$MnpBlqhm;W89
z>_GFR;3GE*ZbsgoBpBzeZlS!#yJo`rt-D38wOd=rZ?38a=SCkJ@vdeODNweca%6jG
z+uj#5oIA9Ps@w%7iswqmClt=u+2IbU|7ShsB?jWe{@M+6SUiZ<*PHge*ZUvvrr59Y
z52QE!>V4r~J6IR}xgqygo)Bd@G<YsNk@lV0(~u#-dWQ~#wzAVIk0dxuH)q=a8&MsF
AssI20
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2118ea3a456ae43f7537af0d83c3cfe04959e6dc
GIT binary patch
literal 45056
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
D=yu?q
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..c5e90c7000
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-revoked.crt__server-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-revoked.pfx b/src/test/ssl/ssl/nss/server-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..a8f9b0b15ba6a5006772309d60546ffcea721ac9
GIT binary patch
literal 3181
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
TbYKJshg&M|@u8^*0s;sCT(as-
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..7f677ebc3519f47fb1af0393737e3c7751d568a2
GIT binary patch
literal 36864
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
EUr6s&82|tP
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..66d43f59a56e3ac78870738b91363408252785e9
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
new file mode 100644
index 0000000000..82255d3f4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.pfx b/src/test/ssl/ssl/nss/server-single-alt-name.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..95616f580e4361c493962ca84bee51baacaf7dbb
GIT binary patch
literal 3213
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server.crl b/src/test/ssl/ssl/nss/server.crl
new file mode 100644
index 0000000000000000000000000000000000000000..769196dda6dfffeb4141ec007d6e276d3afb5e84
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sVYt_DoZU=NKP#(DHi87v@kR@GB-3fF)%fa
z66ZBBGcYkUfpQH*41|~%+0YcBIfJ>0k&z)cx-<6WM&pI2r!*dm3egYCR+DZEcFbs=
zJLCSW&CW;U#5aBOwXHll$=p%k!bPiXhut}MT~u<8-g_X&?EdN?ab}a1JABr;+ig`=
z<67*aH7|L#+SOR4x3|MWJEoZ4vY)6nxx9MOgT+t(bDezlrzuZUzqqQ}z2kL-Q>O0U
zHbE<wjSIh&+P2+I(flFf&)N8NZ@bU8AI59GGNp869L-9#II(HANW;al9|>u9KQFgj
zG1J5K+BS|et`!}!yF)I0+pZ_l{^ZHDOBr?gx7Y$1Zp_zSS614#>}BGLu8NhBoGu3M
o9tXbUKYKSkd*T+M_r-Y*A{D<U7Q|28yuIn;%#d&L*G&^z0V1ELsQ>@~
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..93d58e2651fdf3614ff265dba5629dbb27e6887a
GIT binary patch
literal 28672
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
EA6+1O;Q#;t
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..7493954711430fd0967475631b9380d8399e9ce2
GIT binary patch
literal 36864
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
aNT9M!FAY>S@WFw~MiitB)`a;#m-rj=KB=Yv
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..b81ced09e6
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..00530d98af 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+}
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 1e392b8fbf..679969902e 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -24,19 +24,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -145,12 +165,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,26 +185,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.25.4
On 3 Aug 2020, at 21:18, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 8/3/20 12:46 PM, Andrew Dunstan wrote:On 7/31/20 4:44 PM, Andrew Dunstan wrote:
OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).
Out of curiosity since I'm not familiar with Windows, how hard/easy is it to
install NSS for the purpose of a) hacking on postgres+NSS and b) using postgres
with NSS as the backend?
* strtok_r() isn't available on Windows. We don't use it elsewhere in
the postgres code, and it seemed unnecessary to have reentrant calls
here, so I just replaced it with equivalent strtok() calls.
Fair enough, that makes sense.
* We were missing an NSS implementation of
pgtls_verify_peer_name_matches_certificate_guts(). I supplied a
dummy that's enough to get it building cleanly, but that needs to be
filled in properly.
Interesting, not sure how I could've missed that one.
OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)
Exciting, thanks a lot for helping out on this! I've started to look at the
required documentation changes during vacation, will hopefully be able to post
something soon.
cheers ./daniel
On 8/4/20 5:42 PM, Daniel Gustafsson wrote:
On 3 Aug 2020, at 21:18, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 8/3/20 12:46 PM, Andrew Dunstan wrote:On 7/31/20 4:44 PM, Andrew Dunstan wrote:
OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).Out of curiosity since I'm not familiar with Windows, how hard/easy is it to
install NSS for the purpose of a) hacking on postgres+NSS and b) using postgres
with NSS as the backend?
I've laid out the process at
https://www.2ndquadrant.com/en/blog/nss-on-windows-for-postgresql-development/
OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)Exciting, thanks a lot for helping out on this! I've started to look at the
required documentation changes during vacation, will hopefully be able to post
something soon.
Good. Having got the tests running cleanly on Linux, I'm now going back
to work on that for Windows.
After that I'll look at the hook/callback stuff.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 5 Aug 2020, at 22:38, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 8/4/20 5:42 PM, Daniel Gustafsson wrote:
On 3 Aug 2020, at 21:18, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
On 8/3/20 12:46 PM, Andrew Dunstan wrote:On 7/31/20 4:44 PM, Andrew Dunstan wrote:
OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).Out of curiosity since I'm not familiar with Windows, how hard/easy is it to
install NSS for the purpose of a) hacking on postgres+NSS and b) using postgres
with NSS as the backend?I've laid out the process at
https://www.2ndquadrant.com/en/blog/nss-on-windows-for-postgresql-development/
That's fantastic, thanks for putting that together.
OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)
Turns out the CFBot doesn't like the binary diffs. They are included in this
version too but we should probably drop them again it seems.
Exciting, thanks a lot for helping out on this! I've started to look at the
required documentation changes during vacation, will hopefully be able to post
something soon.Good. Having got the tests running cleanly on Linux, I'm now going back
to work on that for Windows.After that I'll look at the hook/callback stuff.
The attached v9 contains mostly a first stab at getting some documentation
going, it's far from completed but I'd rather share more frequently to not have
local trees deviate too much in case you've had time to hack as well. I had a
few documentation tweaks in the code too, but no real functionality change for
now.
The 0001 patch isn't strictly necessary but it seems reasonable to address the
various ways OpenSSL was spelled out in the docs while at updating the SSL
portions. It essentially ensures that markup around OpenSSL and SSL is used
consistently. I didn't address the linelengths being too long in this patch to
make review easier instead.
cheers ./daniel
Attachments:
0001-docs-consistent-markup-for-OpenSSL-and-SSL-v9.patchapplication/octet-stream; name=0001-docs-consistent-markup-for-OpenSSL-and-SSL-v9.patch; x-unix-mode=0644Download
From 92eff52c83efaa686efd9415ee00eba70f906514 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 29 Aug 2020 01:28:03 +0200
Subject: [PATCH 1/2] docs: consistent markup for OpenSSL and SSL, v9
OpenSSL was mostly referred to with the "productname" tag, but also
with "application" as well without any markup at all. This moves to
using "productname" for all mentions as well as using "acronym" on
most mentions of SSL and its concepts.
---
doc/src/sgml/config.sgml | 6 +++---
doc/src/sgml/libpq.sgml | 24 ++++++++++++------------
doc/src/sgml/pgcrypto.sgml | 8 ++++----
doc/src/sgml/sslinfo.sgml | 2 +-
4 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 606e80df0e..032232e5d7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1261,7 +1261,7 @@ include_dir 'conf.d'
<term><literal>+3DES</literal></term>
<listitem>
<para>
- The OpenSSL default order for <literal>HIGH</literal> is problematic
+ The <productname>OpenSSL</productname> default order for <literal>HIGH</literal> is problematic
because it orders 3DES higher than AES128. This is wrong because
3DES offers less security than AES128, and it is also much
slower. <literal>+3DES</literal> reorders it after all other
@@ -1284,7 +1284,7 @@ include_dir 'conf.d'
</para>
<para>
- Available cipher suite details will vary across OpenSSL versions. Use
+ Available cipher suite details will vary across <productname>OpenSSL</productname> versions. Use
the command
<literal>openssl ciphers -v 'HIGH:MEDIUM:+3DES:!aNULL'</literal> to
see actual details for the currently installed <application>OpenSSL</application>
@@ -1337,7 +1337,7 @@ include_dir 'conf.d'
</para>
<para>
- OpenSSL names for the most common curves are:
+ <productname>OpenSSL</productname> names for the most common curves are:
<literal>prime256v1</literal> (NIST P-256),
<literal>secp384r1</literal> (NIST P-384),
<literal>secp521r1</literal> (NIST P-521).
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index a397073526..b932fdbaae 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -812,7 +812,7 @@ int callback_fn(char *buf, int size, PGconn *conn);
its path will be in <literal>conn->sslkey</literal> when the callback
is invoked. This will be empty if the default key path is being used.
For keys that are engine specifiers, it is up to engine implementations
- whether they use the OpenSSL password callback or define their own handling.
+ whether they use the <productname>OpenSSL</productname> password callback or define their own handling.
</para>
<para>
@@ -1672,13 +1672,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
Specifying this parameter with any non-empty value suppresses the
<literal>Enter PEM pass phrase:</literal>
- prompt that OpenSSL will emit by default when an encrypted client
+ prompt that <productname>OpenSSL</productname> will emit by default when an encrypted client
certificate key is provided to <literal>libpq</literal>.
</para>
<para>
If the key is not encrypted this parameter is ignored. The parameter has no
- effect on keys specified by OpenSSL engines unless the engine uses the
- OpenSSL password callback mechanism for prompts.
+ effect on keys specified by <productname>OpenSSL</productname> engines unless the engine uses the
+ <productname>OpenSSL</productname> password callback mechanism for prompts.
</para>
<para>
There is no environment variable equivalent to this option, and no
@@ -2471,8 +2471,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
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 OpenSSL <literal>SSL</literal> struct.
+ For <productname>OpenSSL</productname>, there is one struct, available under the name "OpenSSL",
+ and it returns a pointer to the <productname>OpenSSL</productname> <literal>SSL</literal> struct.
To use this function, code along the following lines could be used:
<programlisting><![CDATA[
#include <libpq-fe.h>
@@ -2516,7 +2516,7 @@ void *PQgetssl(const PGconn *conn);
<para>
This function is equivalent to <literal>PQsslStruct(conn, "OpenSSL")</literal>. It should
not be used in new applications, because the returned struct is
- specific to OpenSSL and will not be available if another SSL
+ specific to <productname>OpenSSL</productname> and will not be available if another <acronym>SSL</acronym>
implementation is used. To check if a connection uses SSL, call
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
@@ -7666,11 +7666,11 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
<para>
The key may be
stored in cleartext or encrypted with a passphrase using any algorithm supported
- by OpenSSL, like AES-128. If the key is stored encrypted, then the passphrase
+ by <productname>OpenSSL</productname>, like AES-128. If the key is stored encrypted, then the passphrase
may be provided in the <xref linkend="libpq-connect-sslpassword"/> connection
option. If an encrypted key is supplied and the <literal>sslpassword</literal>
option is absent or blank, a password will be prompted for interactively by
- OpenSSL with a <literal>Enter PEM pass phrase:</literal>
+ <productname>OpenSSL</productname> with a <literal>Enter PEM pass phrase:</literal>
prompt if a TTY is available. Applications can override the client certificate
prompt and the handling of the <literal>sslpassword</literal> parameter by supplying
their own key password callback; see
@@ -7936,7 +7936,7 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
<para>
When <parameter>do_ssl</parameter> is non-zero, <application>libpq</application>
- will initialize the <application>OpenSSL</application> library before first
+ will initialize the <productname>OpenSSL</productname> library before first
opening a database connection. When <parameter>do_crypto</parameter> is
non-zero, the <literal>libcrypto</literal> library will be initialized. By
default (if <xref linkend="libpq-PQinitOpenSSL"/> is not called), both libraries
@@ -7945,7 +7945,7 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
</para>
<para>
- If your application uses and initializes either <application>OpenSSL</application>
+ If your application uses and initializes either <productname>OpenSSL</productname>
or its underlying <literal>libcrypto</literal> library, you <emphasis>must</emphasis>
call this function with zeroes for the appropriate parameter(s)
before first opening a database connection. Also be sure that you
@@ -7967,7 +7967,7 @@ void PQinitSSL(int do_ssl);
This function is equivalent to
<literal>PQinitOpenSSL(do_ssl, do_ssl)</literal>.
It is sufficient for applications that initialize both or neither
- of <application>OpenSSL</application> and <literal>libcrypto</literal>.
+ of <productname>OpenSSL</productname> and <literal>libcrypto</literal>.
</para>
<para>
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 6fd645aa70..36ba7b4041 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,7 +45,7 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- OpenSSL, more algorithms are available, as detailed in
+ <productname>OpenSSL</productname>, more algorithms are available, as detailed in
<xref linkend="pgcrypto-with-without-openssl"/>.
</para>
@@ -1162,8 +1162,8 @@ gen_random_uuid() returns uuid
</para>
<para>
- When compiled with OpenSSL, there will be more algorithms available.
- Also public-key encryption functions will be faster as OpenSSL
+ When compiled with <productname>OpenSSL</productname>, there will be more algorithms available.
+ Also public-key encryption functions will be faster as <productname>OpenSSL</productname>
has more optimized BIGNUM functions.
</para>
@@ -1239,7 +1239,7 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm OpenSSL supports is automatically picked up.
+ Any digest algorithm <productname>OpenSSL</productname> supports is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
</para>
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 7d3fcb7167..195949f55a 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -174,7 +174,7 @@
This function returns the value of the specified field in the
certificate subject, or NULL if the field is not present.
Field names are string constants that are
- converted into ASN1 object identifiers using the OpenSSL object
+ converted into ASN1 object identifiers using the <productname>OpenSSL</productname> object
database. The following values are acceptable:
</para>
<literallayout class="monospaced">
--
2.21.1 (Apple Git-122.3)
0002-Support-for-NSS-as-a-TLS-backend-v9.patchapplication/octet-stream; name=0002-Support-for-NSS-as-a-TLS-backend-v9.patch; x-unix-mode=0644Download
From bb4928ad54bd46d34ccc3d3101e965ba5e157262 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 29 Aug 2020 01:24:10 +0200
Subject: [PATCH 2/2] Support for NSS as a TLS backend v9
Daniel Gustafsson, Andrew Dunstan
---
configure | 211 ++++
configure.ac | 30 +
contrib/Makefile | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/acronyms.sgml | 43 +
doc/src/sgml/config.sgml | 21 +-
doc/src/sgml/installation.sgml | 30 +-
doc/src/sgml/libpq.sgml | 27 +-
doc/src/sgml/runtime.sgml | 78 +-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1038 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 984 ++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-encrypted-pem.pfx | Bin 0 -> 3149 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-revoked.pfx | Bin 0 -> 3149 bytes
src/test/ssl/ssl/nss/client.crl | Bin 0 -> 418 bytes
...ient.crt__client-encrypted-pem.key.db.pass | 1 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../nss/client.crt__client.key.db/cert9.db | Bin 0 -> 36864 bytes
.../ssl/nss/client.crt__client.key.db/key4.db | Bin 0 -> 45056 bytes
.../nss/client.crt__client.key.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client.pfx | Bin 0 -> 3149 bytes
.../ssl/ssl/nss/client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/client_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root+client.crl | Bin 0 -> 393 bytes
.../ssl/nss/root+client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+client_ca.crt.db/pkcs11.txt | 5 +
.../ssl/nss/root+server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+server_ca.crt.db/pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../root+server_ca.crt__server.crl.db/key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root.crl | Bin 0 -> 393 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-cn-and-alt-names.pfx | Bin 0 -> 3349 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-cn-only.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-multiple-alt-names.pfx | Bin 0 -> 3325 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-no-names.pfx | Bin 0 -> 3109 bytes
src/test/ssl/ssl/nss/server-password.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-revoked.pfx | Bin 0 -> 3181 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-single-alt-name.pfx | Bin 0 -> 3213 bytes
src/test/ssl/ssl/nss/server.crl | Bin 0 -> 418 bytes
.../ssl/ssl/nss/server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/server_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/t/001_ssltests.pl | 289 ++---
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
106 files changed, 3481 insertions(+), 266 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-encrypted-pem.pfx
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/client.crl
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.pfx
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+client.crl
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root.crl
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.pfx
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-no-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-password.pfx
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.pfx
create mode 100644 src/test/ssl/ssl/nss/server.crl
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550b92..961cb56358 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8898,7 +8898,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 032232e5d7..4aad7a5123 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 552303e211..2343b5805e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -969,6 +969,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -985,6 +1010,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b932fdbaae..ce7cf7cda3 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2471,6 +2471,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct, available under the name "OpenSSL",
and it returns a pointer to the <productname>OpenSSL</productname> <literal>SSL</literal> struct.
To use this function, code along the following lines could be used:
@@ -2493,9 +2495,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2521,6 +2528,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7951,6 +7962,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7977,6 +7993,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 6cda39f3ab..7d6188a794 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2120,15 +2120,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2148,8 +2154,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2239,6 +2250,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2314,7 +2337,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2361,6 +2384,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2488,6 +2519,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 195949f55a..2fe6c70238 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..fe6540852b
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1038 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..f11ddd6b2e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..778393fc3b
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,984 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..e8d1510529438b26c31e0880163aa391bd1870d0
GIT binary patch
literal 36864
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
Lp@HYkDZu{;Ke*53
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..44e6fcf5307a8b2e6cbf9878b157d7cb3e3a3f25
GIT binary patch
literal 36864
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
HZxsFqjRv_Z
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..dfffe92b90
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client+client_ca.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-encrypted-pem.pfx b/src/test/ssl/ssl/nss/client-encrypted-pem.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..4472d88ab737eb98c25d0e5fdc5124533529089d
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3cbd561f8a1ebf20905080adb814e79588c7ef49
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a094273f3761c4c1ecc6a59b28f1147d13b7736
GIT binary patch
literal 36864
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
XbeyC#gN2opHif0+vD$=@ZxsFym1*+x
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..67ce598fd3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client-revoked.crt__client-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-revoked.pfx b/src/test/ssl/ssl/nss/client-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..1ddd9ff2823a5625b32e538ae91d029df841bfeb
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crl b/src/test/ssl/ssl/nss/client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..ec061fd17bd11aad4eea2cb4980cdd9620be5f9e
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sY=etOw9vQsYNBl;=G0yhK5GwhQ=larlwKi
zye4J_CWa<Zu7QYw5ECO4nj$o3FgGzWGQ=5uWWFNvib1cOm7QIfWA#LK*3-R$b#Zr+
zQzt9Of9Pf2QgXg#^7|Q#`?^;~od5q&E_3mbwX*KnbDH;uHx}8yc;&n*Cg`u>*0_IV
z2Y54&+qGrPUC4dt$>ZO*)%q{4x14vAo8!GyW3<{9)zs`N>D*O<h4!Wm&zYD%)*XAW
z?8h?G^mEIM=O3^Yk(m1JZpp$KRu`9gue$prUuR!%Rb1VVhy91s9M8Qw^)xx^n<TUM
zv3Uho7B_OK8=h5cdh)~l<(Z{@Hw(mAHc$Slv&lrgXD46g|EyT1`^j4q@6F3L%d)b)
nrK%&VUs_%AV!{6+)9&7a`}PORtmIRl{k+g!;Q6Fz-w|N|QYfe5
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
new file mode 100644
index 0000000000..3ac5759692
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
@@ -0,0 +1 @@
+dUmmyP^#+
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..969d719dcc131dba358d25d9bcc6ba2c5db0ff6b
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..bc2f47f7a1ef6cb4c6b0b2d0ab69cec35ad4a4f4
GIT binary patch
literal 45056
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
SsQ9RQhx^7M%g+|xF#bPn-f=kq
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
new file mode 100644
index 0000000000..190f880c0a
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client-encrypted-pem.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6c8ec171304048a2a1c7aa14965b30cb7bea2ec8
GIT binary patch
literal 36864
zcmeI530xD$9>;fcbA&~pf~XJ?1p#$)K{!-S5kw4!C@NwI5rZ6-Bnq~oL==x!1dGQ*
zi+~m?Sa=|USCzKVs`#u{tXip9tmP?%id9juzMV}#KzsO+zSqy|tnBQ6c4mG%^Udtv
z-PzeBBq%_xlyMd%CdEsX96n-*peW+b;UEa2qg{y&xzLG_rnSKa?PLF%se??wUD#L`
z!qEK%(bvK1^y>9i>$wvVxIhAs03-kjKmw2eBmfCO0{?Xa?hJ;hsU;dNPl%Q!OP5IG
z(FqELSQQl~momMAeLO;aIDUcNK65x^8fRu8r#GErE8*h2ag*t!!YooDP7)=H>&?OF
zr$D9+RN#;ErZG&1TcQzUd4(z}Rwh;UD$x5WXj2DjaB=Q94=TfSSWi3T3WZ9R)GI>k
zC!$Fls3GEx%Wy`Srk0lIW)Gz#Do)m0yw{*;bwWKn1AO{&kVkXG!G1G5g2Oq}eZo1j
z0{wz!`G6yM%?z9x;O7;}@$j4(9O}T4;Ka%$RvxkPflclJd$?ei2t-97Dk7sGi%0+h
zAqX5n-~<8@2%JG60YMZIIFnMiCL|Scpuxp8#{nu_9LR8Spu@$15ElnZTpUPoaiGP;
zffyH;f>2vMP5nq^p2jAXd89IrROXS&JW`oQD)UHXK3R%Smg1A8_+%+QS;~=2btF?A
z$y7%&)lrj5YC4jdPGnA!50?bQBU{SDL0KNzQXW}b0ZuGDF4(O#fi9oS%_np7$=pOD
znMIQk<Pngh1UNXhfFva#NeRfK3do}h$j?PcmKBm}LQ+jgmX*qcd;wnwB()~c)taDT
zS`!>WYl0?fP0&uQ2>@zMKv83oP_*XrVk92z-si;}dx|+euMT1MREIEoszaDP)gjCt
zUBc|C4q^7_5@sJ=&}YDbiM^oXXid<2v?l06S~L2&DSK`%>NUxZpvfEcygE^!I?+N2
z9vwvz7lF?fC$WRP0=^^JZ+Xt+xD<w|k0pv|oXDGT#2tO4M5Rn5<B`2*a3r_aq!O-{
z1>7yTsYkIVz|_%ffC(2!01|)%AOT1K5`Y9C0Z0H6fCL}`NB|P}{~^F)=n{=#VxJ&b
z7uJE@!kVy)*cl=U7f1jSfCL}`NB|Om1Rw!O01|)%AOT1K68K9IU{Pr(U5BxSNgAx>
zBN$p^I9+21D`{Gzf=U|9?i>n}O4Fe;yWb=4{|%-g2Gg)H22BPz1~af~EdDRmKv)?_
z01|)%AOT1K5`Y9C0Z0H6fCM0czYqZ$6+xN4v@}B$cjr#V-5AtaG^)NZRHjgJk`fb@
z94`;>k~v45s8Gfv$wGnxI3XbcoFo|$Q7Ghz2^=NJs!$Pg*BOJPLmwm^+92sr{b*?>
zDDKI1$0uv}IFWq*7#eAuT$Z5ZNM%V%1!Wqcq%#;wn&<zn2zDLYf_Y&#v1wQi=K2@v
zSg<OP03-kjKmw2eBmfCO0+0YC00}?>kih?z0F!Ej?nCA>!O+K`4Osv&+MtaSP=Op}
zIMq!DAIdesjWnYUdJ07x7;RB0WJ#L)|9uEC@4pIbC*A;Pzz$&h{<mP^(IEjy01|)%
zAOT1K5`Y9C0Z0H6fCM0cza@d5kp}ISF_em<U{Zy>W==q)R5C%Dq@-(;X+x-E(S9jZ
zZ3<-w)mDeN`Jbps1%)*C|C<r41*^k~uu`lQ+l-z5TT+INh6Er1NB|Om1Rw!O01|)%
zAOT1K5`YB$O9afQ0@S_u?iH_(byEVuP^R$x)3uDvXM8ks3_vQQG+?Hehus@(K9o;y
zvx=X-`HoaUCnpVn)X+q3%c+!xKzaR-^&iqHLDzymUZ$A?U`iF}yiA!*w2XlSA^O&O
zmW<2vMl^<qyw0LFW+c;03=E8bNSdMO)wB#=DZ2@=fC2^r2e6?NUw-hf5U&svA*N-#
z{%a@{6GN7%O;_%xdmCr?+x|lw=k|GHU<gVaA-L~~Ot)Pm|8`Av;_j(U!ON?&PVnx?
zhsD?we-PWw&8k1Jdg^SUsn_}?^c8>#j=UpAO!{L<GZcW@|K{>)Tn0!HT!s_7h#z)*
zanJ}?Q!Q{cg|UZ9K`Ae9G0tY_*sYtk?jajR_2|g!gT*aO*~HJ7aCuH#KJil{g1M$_
zfg>*93bfHdID#6r>RWA;j!@MohTv*cm!c*>9D4gw=SJGjH`Q(v9MwnKHnWVcU7vR7
z=j3J0F^o6a^P6_eE=0{g&yhQIeR5P-^2Gb#hKEh&xhwA_=1~HcyGqu|7`t7&y7eO6
zKKxKVO7htH;w`gG+fPcxZ52*BqmzSlwtAmyGb>L#pjx}(F>R*%_jwspEnQ>l(w4ID
znzgs*Z#(;Rf#}@kcQ@8OZL6%UG#~57Sn?Auy7Xb~j?9v2UmaFA?nu?QuS8wyEd7_D
z6{}h^tUbN>lM5mydd{;yGN=2<>BYN^Kh3z(-279Dv+a<MOIAA>F{m})#_YXgC#j_c
zk0utBPc)L9J+i}h>Z2VsZ`+Qj^^DHH#(MBplxxg_8>&JI6-CfOL)-w@C*}l=G^WvU
zDvCbq%Z&J_31%oV9e1T{Yqwi9X3K=1Z_}n<KU96)`bx~{mY9r~WA?GF|BlaDed?zT
zO$8Tn9ab8N-MJq<G#{!M-$sAB&-6k0LjU+ozQQ~+^~}9ltqVU-GjAMfT|TQO<J*?Z
zuqyA8y#XcZA`h#5$CeiQpQ-EWP!&Dc<gzP0SI_)X>(X_R=gYg7IezwlziF#Q+v4MH
z#S>||)lZLnWAWB^r*!Mg;x3zgp&oBMp&~40ZTITRhpO>;vspfmcb0_*TwZs`;n@PV
zbHQQDMNjVWvu-9H%WB<q^}Kb;e6`-35v$9h>_$E8I_Y3Kwl*(+hHqwAX-#@_n$5S5
zbds;zh9u*A5mobY!}bOA)zl_%GQEC}{FPJd_1~3y$^Qqa8lm;|!K5o5;1hl$`f9%B
z1s^P|984dKQeJepPfa5*y9Mt1pWWX~br899w`8VHvf`B&k8Fq?8NGGyx+vL-mFvB!
zq{AJHyfXnf+`bIbO#XC-E36;@6DLo<VR*vVvI!4#U*l_0%7C-&^*h-B$13daScP*3
z<Ap(@0l)29kN#lMd6=ELx@>e~;So$;e>(o3mv6*ua+T#-U^%jF8$0cz<|o}T9F<xX
zk;%GtOFCX;*Ju|Wr<|C5(f78_d+lGh($CvW{l2!{COUr2Myv9JYrlz4{ACFy`qI4d
zt6beTN^NxBzgIPPXvHtgxPpE6t0n~m-2doVQKYB0Db?${v-57zOOPSVQS~MBEn_bz
z_MS72=-6(gjGT92`;=veC+}Uc;;z_LbPavBg|>SX;u|3rMJ#mBtqdxmY;QAMnozqe
zS2?Qd(j@w`fL&Q<l2kFZ+hy~1HnpYJ23R;Pcw{9h8L|J4S@y&{X`aO}yCpIALpMwQ
z`2o$wL#00I<76{N@98VJR*TAv&ZUo=s=tX4%yeDSL0N3OiaK#ljDP&M?HhJy`s^F`
zWM{3*nwo~%YpHt9<1SEVm==vj%qX6BtfkXW$3Nb2`DX5lf}8tSm9k6x)?N^;XSWKs
z-hJ0dwCD35S%DvXmgAYy${e@HSmKIEMb<tU>V%4{E5)j2@g-v;8%j=9YR)cOT&>&K
zzE(2StN6_HC#j68EflNRvZ-E=V&`sIQ{yK-tB4auUM<R%W?1^S8?1Ca;$pC|^aSPT
z2e*A=4xBIhD&exPbduQ2aY%mXg(;i1?MV!1+}1{YaMn0xf6~Fxk1ixrqF811V>)Ah
zD!d!+-(}%&N-V8npRA%*>mT>@1WxGndp&XCG8i5EW!KKAnnLO0Nlp5Ck>Bv3JxQ<U
zNr|uNdhSUF<E3E~ebJXPuC}Y{SZdpD-zg=vDZGvb$q=_wU+7k0EyoVg)Gi}ST<2cN
z2uD-E#j7o{$${{t{V2Ho*{+oMjxqu#;Y#~Iw<NAv8wgi)kb<HE-RK~o`dvr*E3>J8
z*Et^Cv}Zzo=Sg?k3F~mPI`^zA!iKSjO~Nuo9uf<Vei0gIm}Ygu{^kSkY@Z*-CU3W0
zK0eOjDt-UOlZm-=7P~M;g+(gP-K<Y^NciltXGwwOEz4P_3!QaXjoF3lQ7ZM-djCZS
z)GnpGl$qk4bfZsi3^jOn#?$a%Zj{IO>D$Izy6--*wY_0<`oU^{FYD~EkMfhZ7iFtE
z?90tf=f7PeD(ByxR9z-_ek0e#^6_F$X3B=L^v)s2{3{l5%iMPFjVP({op;tT=0opw
z$}qu{1Nw`9Jil%0su8Jr#>v5=;R$OtZ1}k|E2!lt9`yBhe23-S{4JZN%pNVjxSv0H
z-oLCu%+KGGlFo6!2S#3Hj^M<`wGM0Z9mc+OTCyg8V>RAU6lL<oox}OfOl9_0H?zXi
z3o6s`F1yiXk5rY)<wlf<ySzPiyJiOm*f0C|>&YL@5WO`f%$m}<YCksfz#DnFba|;P
zkezyHSpdsn7iR2|bMA4$+s#FLF3ySNZ=>ZWU#6TY4DC>b)O<1W@wqP#yzi@f+`x_T
zP#!HlQ!`7}Qndcd*>fvTu*PTCcJteXPRlw^hNrQEAIOZOk6I`hNkQw!9Pcg`7wx~3
zt(G1?HR<OG^75jUH-C6o=JYk&y&+VwI)CBVf5hF`9<;~LiRZqs*2KcHxT~_+B%*-n
zx2a8RvN3OV1;4<RX`gUM{}=2Ka2bQ)#lZRSE1mOYxBn=(|3?jGAjBVBAOT1K5`Y9C
z0Z0H6fCL}`NB|Om1R#MwMqmxjd_G#%w}^?605Kr`5szw#C&NbR`pAHhG%~4giC4A3
jH{cUxDCDzWP?&fG{L&j<Vh{|Z5r2R8u^8eBAcFn}W#{#A
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..73cc753ff42454b2d6333afa7fe3470ef78d4d9e
GIT binary patch
literal 45056
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
Zvrc*RxPLw*z1dFh*QOKE^1i+;{|DZOS-t=O
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..3f0a9ff5b5
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.pfx b/src/test/ssl/ssl/nss/client.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..9c5f5bb3e5ce60d158e87d7b9b114f29991a7d32
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..710f74aae2716883ec8e8bc4f8490b5f37063abd
GIT binary patch
literal 28672
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
F@Oy<<VN?JB
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..13d47f000664f2003064ae9110f2f5a07013494d
GIT binary patch
literal 36864
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
Z(ZX)CJX)CJWzoVGB-O~0V#1#&{0kpicC`Qi
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..212e72edeb
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+client.crl b/src/test/ssl/ssl/nss/root+client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..480e281015f26cae72cf86ba29e62a4fac1288bf
GIT binary patch
literal 28672
zcmeI43vkm#9>@QWByC#S^eCFzQ~H;y(n2Zwe<V$5hiTfTPzY`5gNGtDNka`3LYqX2
zqP97phkEolPp60sT0n8A+5zNIaZcnaK1Lnygz;6VaCg(=I=~zbEl5vyH~*vsA2STx
zjXJ-b+0A$VyZhbWezN~eHp!%7w%y}*3yZuBOC5eeW)c{dW%2}pVHh6gD4fGdi-jg!
z!H?_+{X>#xGSQSc!yu!J`7bj{Z|IJFE4C>%4_6=o0U!VbfB+Bx0zd!=0D=EIfjphg
zXiQ<}dzRI>8(m8ro|<JoUs=Fe=W#_9l-sP8Hletr&^Aw?Z9-{@5NQ|E93qm^r)sG~
z6?Ld{INf!T9=a<WXxr5ea(bagXZ&>vdk-D&3pf|MUH*uJ_6i5J^=c0hiRo5eXS^l6
z4v)_la5qFuG*_5}8n5=Si0LiaY?Lu2g<Ws;JDhdy$nc2ZRG&&~zTGyGgQrF)D=(g9
zEuSyUw9OZ)N{VM!*~kn9r6oo7;(|)SnqOL8nISk3&LYkd&N9*T0=XiROBPbKkgA1N
zNf(Pk3KJ>Jq{t$Lg%sJOaFD`@i)?CzLM3%X#6v`(d5DXMh>eJdkBEqoh=`Mjh?R(l
zmxzd&h+L#p!wbbnou!bb&JuN&sIx?!CF(3uXNfw?bd*d-$#j%VN6B=QnYNl~tC_Z%
zX{$NZN<GcgGmG}5d_)RJqAQgU87tA1N;Fyp;UtOVvMPzMOnb|;w@iEEM%pFRk@Qh0
zDFu<a6-r8>q!c=<LT6Rz^J1c7P1Maq-Ar_>%WaYs*+eW=NqkjF7N$xvgDS}qRY}&V
zN&--ogknfiD5@MDM)9auhliQN!_33sU>Obv%WybYhQq-!?2BbM94y1WSdQ=|dxlJm
zuab?UO0s)YNw$zGYlfwJST2f4x)DNsox|ZcNjNno2dZ&W;uiAUB8r{#Rb(^Ww^DYx
z$mxu>6qX6Sh}75N6MeNK;P=vcb>t4N79*0J?W4)dZ5+;A%qk}KkGOyY1b_e#00KY&
z2mk>f00jPr1cHfNqh?He{Ae;qbAsjCri#C&?qhHw1LBno@}P~f%>AIEpn_$#Gk2jp
zaVpBy@l_grl&R9~^9v1LuU{yzlCQ;tGOy2H+u*L4Z5Jvk>_USZSA0H?cbVWPU3~%k
zZkrpE5N#Ykxash=qqAnDJzZ9pd*o<I1&e1WADNk%X^T9^f4k4Sxu~an#l8)Hmj3Oz
zrFQ&|=NF$5H@x*yQ_)<LvEX-0*arv|#5`WcjJXm^bAm#m|Iu`9T&}XhT;||Q_&Y#?
zV4|ze<6h>kb~!R#4Sp2l$D$yodzR-|?)z7a;&l26Yp1Urh-2AUEpz=~(O6>~KJkiD
zmMG&>sdASwPB9}zRMhGXm?6lH8yPmMW%wX#U{H{a;er^%-c`r`^QdNHeK0rE9PH{}
zAD#HciRpX4YP`R<R#zXlpr>u_RyO&_lb)=>H(xXD{JL<@x`CeLO%I*-ZsF`JavW>i
zy3IL*mt(7QUwqMXyW>*o$Nx-fO?$JWtbbRQ{x^-Y^-mOj*q_wteJQYJ-6c(F-aodq
z6s6=W?rvTljrOlOz2K?$hZb5sT>tw=yNCLBAKIOKXR&U{IjN>&;80uZ&gp+U5IoxU
z@TiHq*(u#AGnTNs9_edI%`cFrw%;>3|L%!h^DcM2d*9~7!!4io_MTgro%XA<$Hr~c
z)v~Gb_@qA_e1q?3zc{(QbMk2S`(16*iY~V8UzK+2q5PWXzKFi?pfjg-;g^A}9M3ZB
z)&vxfM&VoJwnU8<@htnzNM`t{#>`k|Cd%QSIy2#sJN}UQ)oIPl6MOfaNd2_--BYzK
zwFf6I?)%r$C!5|mx2~uCubVO+8eNtrzC4h8vu{$rc4)itLg&3RmbS{i<kpAZJ73jz
z?~&%@qc^8^R_$*&eyVlOtA#ta*>|qCSjTNYxP0r3_qqqq241-E*pz2hZ;DMm*0+3Z
z^+%nT?>GPDg8bMMWBc!WJ$J`sP0YTbuFuClc=DZ??xea;lHLwZO3d6fXXTp9O}hsI
zleWx_wq4rz%6$7LYxib+voJ2Z{Xoj1ug}XH{_Z`vq3@Z`K1yA=AQ(IE)}~jS6K)?E
zd?Ukn=b<gn&6?Iar(^%>-sbVgFX|glq*XMcZA_qdMZ%ed+9tk-+)UTMBY)u5y6zV#
zU5CB2z&hckej9iAbo7p)`o~>9HU@{x8O}0>L0mur0zd!=00AHX1b_e#00KY&2mk>f
z00e#s1fqEjOFsMLqIiv-eApK{|JO5y1BUuv!UACpKmZ5;0U!VbfB+Bx0zd!=00AHX
z1bz|(rfHfJ*c<R8fg<dqzkZ+4`F{^%*kd^Gli&kE00AHX1b_e#00KY&2mk>f00e*l
z5C8&{fQDyS?uMTKPcnwnhLeC72mk>f00e*l5C8%|00;m9AOHk_01)`O5{TkQv-nR=
zxSM#S|L&i9gueej#TZT*`hKn}gt&nK5C8%|00;m9AOHk_01yBIKmZ5;0fzouz{o55
J@B0J5Zvjw2gWLcB
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..65f7eb7f0b7bf0b581fab253b06484fe09083650
GIT binary patch
literal 36864
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
a9j)v#tD=<!Q5mglK?=PxAtwA2g?|BX=!#$f
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..bdbedc732b
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..cdec09a0dbe8b127c8725dc180b281a355da3530
GIT binary patch
literal 28672
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
Jzv~YGzX1y<hUfqQ
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..d7704c2b80a3a607f026118c205c48afbb98bfe8
GIT binary patch
literal 36864
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
Y7CB|8GB1RODw|N4E<7PJ@Dqi90gcpt>i_@%
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..5d04ea23ce
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..433b1b91c6a7891f52cdcd3f87655486798c5b99
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..065152e027a94fc9ba48159ed8cfa0fc22a131c9
GIT binary patch
literal 36864
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
Y$_`Eus>}=WP-P<u)5v2Y0zXms7q(k}82|tP
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..9e34f7d8ea
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__root+server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..dba82085f965c11ae464328fb65e8017f3b8199a
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..de0f7b37cc0bb1e6b656089aea58a8916d64b3f1
GIT binary patch
literal 36864
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
WTcD$bMM)DaY(Z*+#vB{|MB!gEICTvG
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..b54121fc4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root.crl b/src/test/ssl/ssl/nss/root.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6bccb020a17e1825bfc26b0de2213c63422bfe8c
GIT binary patch
literal 36864
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
SdnmMpl~!>q(ZQF?u>BuKN`Sfm
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2d3a91e75b37e4fc925ee9cdf4053fe8fbd9a7ef
GIT binary patch
literal 45056
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
Q{jqP?6(3YvVN%olKUb%Z00000
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..bdd10448f3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx b/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..b75c8b1df5d958af6e508211fd2f29534fff4d21
GIT binary patch
literal 3349
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..4ad867c6d8c55be766ae060df33685bea252b12e
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..b49070fb6791118876f04e1bb84a73c4165c7507
GIT binary patch
literal 36864
zcmeI5c|276|Ho$^GnlbOvNR@p(wR+Ti4bkd-X6^`M79`&lBp~ua&N^qWowg?(kjY&
zD|ORNyA%=CO;OQT3-z6IhM#-Cb%*bL{QkJ#`~4g<pLw6p`}297*XMc8d^mF+&bYfe
z1qcN+ztHeto{+{uDWK43)C?L8g+h^rp5hl|A&5)dPz=AIe-r;vNJ5!H7V^{&C>gRF
zN)AV@ldY1CmgSJ&lHJJFf7A)o0|I~mAOHve0)PM@00{gy5eOm@)wHzGqRB!YKS&TP
z5b|d7guLNb8GAPeTMq}ChpnBH18umLW*Fd0b8(w9-_~sjZJxstn&*N!uAUAw=LIx-
z=LL>VbL>56ZVoO^w)PGtH1Xf7!3@a@(`c#;LCszZjS2|y6|9X|6C_T0^CE?z$h!Bi
zzBgkyBH0lLYHB*@Wk>^`)w}@TkcbGE@PH_uP(U0qH)p|2hs87`30oLxKr`eqAg0j_
zJfg7((Fo%41wkWC2*1-nlHX{sj2v+UH4PoKx5RcNKTzN!9MQo4P9s8+{zioXF^uf7
z1U2>H(F8<9L<+)3WN^Qekz~J7;WCVpt<Xd@9UXMKJ+jM21V$pvkd$P%jnt22$JyFB
zyLp(<c#!xh-fujn_{oA}qynyCz*Ss$l?$(OkyW?}mkk#jxG;l@X>h@X3v;;O!3AGj
zm?Kh<q(l@USc3scwgam$AXtV0!8!~G7GglK5(9#z7!a(*fM78O<O7#O_9XrgWu_!X
zl$nS!6H#U&%1lI=i6}D>Wfszlh4f+}y;w*u7ShWM$(kWqGbC$<WX&X5MAHn>oQAYS
z_%IMaOk|`?2=2>7M#@CovLW%pWWd!!A*{<nTC<SWETpwKi8PTkgxjzYQfvs`HX9+u
zMo6)dt+J7=vXTA5LHcqKH4dW2LHha#I4m}c14|Btu<lR@4{Ruew=fjKLmUd>aSnwr
zz@ZSPC<zfLL*Ym_1kX_INH??LZe}BP#Nn_VaX4&891hzNhr_z!aM+GG9M%<wztM${
z8F<6uTKK>j3gM$?D1;B8q0o0E6pVy^{}&<$f}|~f#10>}<ICYezI=o@7v8rJf*o$l
zW|<+!Ez{hHg(0ZT()pt)aOAz}%@~QX;@>DT@NX4)@e?hhiV{D-0tf&CfB+x>2mk{A
z(*(BrqeW<(3WdV`cMK{bO*RE3{-ID%D2kHOW;v8nB5LEh;h&-Q;xydqPcC9n6bet-
z0bgyvqR|l%K~p0H;ZcI{DLx@nLPLULOnt(Iur}n#(1tV#SWld++&Gue2%&$tz}?k}
zCKN;nX%Ug)lR<_qM2GWOISrrC;NZ}ZDIwx>QPVL0DPmQ)nJHYH&%HG7-L_X7Y6>+o
zb&h4B*t}p1G`ye=qLzvucTair>UNuYe+<@EEOb@wxQ%I+ne2?oIHi(nX?cA&BR8ge
z=E_Aj+Q%n{>7r69t3$@`Ui-jiO$k%)p{e=3<Ny}lpx!|)EtuVwt5VVu_7uH@8mFOD
zJh0E$D6#ATHr_V3^A36SO<L3BUGAxm-|I8D)pVL0roEX9oypYuVn5Jd5IK2oW4BXW
zPo!B?Z~vUms2h6PpEu2Vwl!zBySBehE3wV%OD6}TPt$CzUGd4NwRy4DM3jR=>Fahu
zz&_DFn?ARKukTZirrl;=@b9YIXt!-!+}q1(8I6PM3|mSq(kWFba!O0}2d^G#UHNX$
zkz+~u%gY0{98x-(O~G_~pGTw6&_=WwWJr)TPMV$6FOQbPqp^@WPDNqe4{jghI%cfa
zvaV2=t5bBkIou9|M&mZ3Wkhc>D-)dy)CF=IPyYB;-t&Zo>U5W152Ev&yKlK($~%)3
zS+Qld*XkC#bxY8tiQe4gyMr36pr`v@spO8|(NXLlc2c+f{o04yZ^r4=I8hEIAG9cl
z93NBYdG*c&cCCT4A1{j%+8G~WkE2#kZ$*3g9aFpf!~oKnm!#LB^|feRo`*BH%Bv=I
zKicL)sVGS}F3{xDrxPDf&w5L+6TYAD`E^3itPDq5oo|W5iCcv$Jx-kDT(~;%{<GMe
zq*_zRu%z5i+2q_d#X@wh)fxRsZ8cjh;yxVrYTxW^7hZYTUL*cQpkn(H{x*jx?RT}`
z;$q`#?$bQd26Hdv%|3{l5Nn0n6z6ZO{a*EODI_}c9dL(<=gGA-GMRb02}yMyw4=Wg
z!>wU(qLh{<8KblYQ-_d%7<*kSQOHGJ(CuB!>#3D7?hg&#mYjKMx1!U}xIoj?*6~1m
zsVc!}pQ+pqn)hYee0<%S85SueghUQgLp!i+SM08)#`~9a9@I|Ko7aokyOevYR;Ouw
zYFy}mj8gBThtnd|GnX`ZDui5rVQqL|!obYMEAm@pHKJz|Ck`mxT=;$8UH4wcFU!vN
zv|jY6qUS*d=_AoPN8(#<+?iLuUR=TLPJMCCWP|7Zo-OATc0aZxG;rVM{Zcu}bovjc
zJZ<B%ugu!8+t|mM_tn_mXgfXM2gTaa)$~%dr=@#bbn?&hjc1=JJ!(*+88c(k0l}NI
z7x>c+ERFUT?A6J{h-Q<%q*<jON8NawRXQzga|x-@^>jd!411APm-a`O@s*wm8Y!N!
z-}gPUb@GN(|D1&{Y*U}D*DZe9es<MfiEpcfiXT+XsvJT*E;m%V_$V!#?;pNqO0dQX
zeMfBU?uPof@P~u;dw${0Q8So5*TG@u<x1rPas7g};P$L^-<=)kD=Q8=6h0XE)mA@n
zeNQ->SoQ>aSmo~S@W$EzqRO<>Tx+>C$!d0kXQ#d{(sl~pcQ2$@mE-YbL-V}Nzj$tF
zUU#kuoqc{Tp&{e@zKzqHUg|HJj<L|dM~U(@?k;5M%1C@`lwhrJM8@*6N7&B0?bNr^
zbKGcIu8(8$<C1+N*SzB5k8Up*w^FOr;IeN<6lqS&DZh*Llipu3FSnh)casg;`h-GI
zLJ@Pu6*-?~jaC&!NZtAnbm7aC-MdY4)KX90diXv^3o1>z_VP#v&NAc9EKKeUw^CNd
z)j4%vV;`vZZW!OAiJNtBSzN{UeXmscM~M6M2E)g<Gj4}>U!O*Pu_Zy`dvMi${*P@(
zMRRPP_Y?Bw;>ct?B(ONL^Xrc4yT{}ruO6jiTxJXPiaYyIzF7|-_nVl~Gqp)oiuqcL
zYCB){H$4hrhnJVz%R{Ti_p`R!9PI1YyBad7A-8;~%{u+9ZjTSD&UUPwy49I}d70zG
z*dn5;(Z&OrN5+|ZYx~|8q-R#`T&0<$t>m){zJ87VU!U2@Fa+`SdGP}*fB+x>2mk_r
z03ZMe00MvjAOHve0)PM@@HZns#NtTsC+w2*{{a+r;BPiSFdRSt5C8-K0YCr{00aO5
zKmZT`1ONd*01!Y3V6iBY<oy2>ih2q_0R#X6KmZT`1ONd*01yBK00BS%5C8-Kfxi#|
zC!9!=^#AprZ<6!>XDI5kzt9MQ6+i$G00aO5KmZT`1ONd*01yBK00BS%5cm@W)UjOB
z_wS-3IsbowqQ3YOvY-nf00;mAfB+x>2mk_r03ZMe00MvjAOHybr3k2D*`)8?<rcpG
zzcP-9qUKRPQ9R{O$ji$4$=1kflA}qtNK<4|h);>ugx&b}cvoDpxEWXg0YKpIM?geZ
z#@lXGLPZ4ld;18&h29Vc5}30fj<@7nN0>}VlmIO!kcMvmjHf`(knPB-9K^=a$_w-M
zZul@eGvUl~`GB~Y*Jk#G<lQUR^va9rCpzl>>>|GXD+W!2$OIXQ3WoIC7)L8^UPiPV
zER6BAtW#>>TI{uSSryEaeg0s2^<wO=^WSQT=t|N}LSrzQG<}_EJbCNR>8|q*sSB~8
z`u-Nj-ebBri4(T9H|Zuhjb?J(iay!w4(rN){+xZvy>0M7$JGyA7Vi%HYK=SSYvc6p
znn*_xZ!<)4wIF5~B>O)B`Bih*rq@44sBZ5fb{0_HO*E{$w;db54x(J%rB|)nwtqB_
z_Lr)%t<DX+o^rk+X1jX4p7OIB%~KEg?<}st1Xzy~DvERzq<S0+$A21L3XYnvHY~#p
zC+iT08`ycxOZnxFj-Kbii9f$`WPTdWqlNQ|fvAV*-V0u>RN)M*>(jJ;uw5vd)nz4I
zH+Mawu0%wqe!H`oeD*Z)@riuzj`<jNwn5G~l2(x(Q{&pX19@RRv^VNIXJw-=UD(<&
zKVn{4*rd@+ZV}{?n88#}+s@;*nU$I5`SUpKm{%W;X1f%$8EpA-P(-IlH_07?$)-Ug
znZ+HspUWJ=7bb-skDO)j<c6|pTYy^kpda4(C45(Ksc#!r_SD4BGqWL!=rni7;H+G7
zeaJykh1aVEWJ2=x>vQ@=ba|;J`J6GB{Cvwe*?;DF8`C!vP8`18TIBdqS6!8wW0A7?
zaNt}h-eNS90j;O_Dl65<)hUS%G0v1%x62K!H}bx&Dz$r1cJj0FHW6J;s!0xi3?_9d
z(huxhc14lpTYUlj(4AU-#4YZ|`1Vs9WBV&h49OoxGwJ$nS%^=w;^~Gus%pL8=a2RI
zRqvwK;GdER`sJC`$DfGkvQka**<&!dj7o8P%)e97=hEG5XN-%_X}o!)Cu+Ta+CTEN
zA1Swcjb`$P)uuWMzG{;yf6;1)?LXh8R<=Q4hps#JW%&!!il}!#iRfghCdFSVHTqP3
z+Fc}Avnu_gw=OojjLz|IEx4%o$(v$%@7XEVql@&LqnT{kTJwQnO}kgzQ+$Q=D80*|
z{Sw+F)+Fof%>{=Vql*@c=p?Bo&BihXE!*Td{_@mWDuMg=HdO{`yz5OMK3VM89pAo3
z-tB_p(~8kdT88;=i6UR|B`f6DOt?kno_rkLWZ|;&{o~isZ^(VkCL+4bHzs`r<^uEo
zWD5GgI&4?tW%W~<V|Nj~2+FjGJdg7mG~pGaUH6=LI`8mkCL2-fH}b3c`fPuie6u_L
ziKz0vm+_S6Gx4jVo+u?K36({3qI8pEnS$yb+U&hjyKlqAj``QFncnr_T&WJ*p|vfr
z=k=_)jfKnM8_r9ef(k>+5=|iATNwsh8vTM7r0hvu{rs+-VH>_-;rd%)T$YGVkZN*F
zQ&34^$%N!}*CrM`DZR8WGOXjq#Y)$Dy)|u*rp5o1lP=#rnn_oJQ(0Vkb+Kz|%=#5&
z59@N4zTTo}-XXK6RxTTw7{L|M@ls8WX$soar&M9S!sv^>?$SF2^*y`tSIhLuS+4YO
z{-Abl^8MME(M%RT_1C-f&y2o^t(TvVFP<^K)J<VEbwc*p6UVs<oAR6QiRd_~CdV`d
z(HhjRf6X{oF28G=>zNCok%Fd^C3<1>sym;9iQd^Q{iB&IF73KP+&($=RF|&Ijy>U)
zORP*(K6eG4JguOyC+$E9Q$)u~H94j!D5UoNfSkdPbauVnxlLx9HGEZfD4VvgI5OC?
zX^(HR4R17)^Q;7w39b{k={d(5g>rH0k9M3CX*RW9$i-F7_n_>F7SS<MO^#^_GWU0D
zp{&`GjqhxkzqEJBJD0<b7N>U?RdjDJsmofNNEpqe{1lPa{<DEO_f#JE7@Jk-Y)&uk
zWebW*+q|c{olTUj@J8dc*dm!QUPMH6Xt=KkmBC)C5$JwVZb56-*4mW)+Hc5^)px#K
z<e<E*kJIt<`*m}Ax_^nU@V}uaqKUoW6z(lNzsxB5d2je(N@m)^*hu*Lzp{KiirPoD
zplVRhQrA-NQnRQ_C_IWC<uN6XGC--MB*;IPuczoz2!Fp5A21?701yBK00BS%5C8-K
z0YCr{00jQq2xwz%(SLmLMp<eaQqscOAickP!9r157E<^|`gb4FQ>CW46lrNbM_yW*
z!<Unm=Cfs`r8zvZw6qyS`uFtjKl+oAn&uLvrTH9!v^0m0mzL(UanjNp9#&e~48q7_
KwMfICDEtdt8Pm1^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1e4d3f5fef
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-cn-only.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..b0f30f7506ed349ff8119b5029a8818cbb04b01e
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..54b86a5ec169812a42836dff088af74ac922aaa2
GIT binary patch
literal 45056
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
Cb&rYw
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
new file mode 100644
index 0000000000..142748c1c9
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-password.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.pfx b/src/test/ssl/ssl/nss/server-cn-only.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..ba5bdfaeda2e17f36e42fe551cce074910c12a62
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..9a302235cfb80c46a1bfc408e43e5a99205fc250
GIT binary patch
literal 36864
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
Ra62=tdSAS)x3Qx2e*j|bcw7Jg
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..8907e03577f91c6303d4b523aa88a3d23eb6f48c
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1d08f78285
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx b/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..f90123e979d9373e3057b4cc47ee276b1ed4304a
GIT binary patch
literal 3325
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
H0s;sCd=XR|
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..16075e71f320303d91db3e74cafa01df05fbba6f
GIT binary patch
literal 36864
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
IX<`BVFWqWKA^-pY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..ef4a50a9526fc10ac8140fe4a46e98d7cf727c01
GIT binary patch
literal 45056
zcmeI5c|276|Ho&{ScX|5J2A#CYG%v~5?MoKiG)(jSW31Sl9Cz`ilVxeizrmuOZtYi
zP`1iVi@J(v6{TdUnDm`<aNWM$J2Q{pU*G%veU6#WdCz%&KCkooJkObr`JD4$JQgns
zi3uVGM@EM;V~ECxX$T||VMQb&5D2M>CvOEWV!RSP5ko8FpW?p@r4X~ob8v(&2nk7d
z1Qt!GmAWbwCq<X+mvooB_IHiIc0d3S00aO5KmZT`1OS155`i#rafKN(kerz@OjcM>
zcu)+}jv2$8Tua!x+gmTOCoZwJS!7R~+)C672_(9?J2_js`w$n}`w%@{oECf96J1@1
zwyrMo7dhE3A-dbUEwZ+?Hze{tuZB~`S5*Dk5@HIrGmwaoh`^xr?5Hqa(vKM%6A8ce
zo7DHCOhy<RF);-N4dimTLO>`pBrt-_c8d;K$BYRQpD^d-VrTD7gp-hksR~3rCWTDZ
zw-SXlykLzmCMzgxs*2bT8gTL_4P*WJXfXv94W!?=?O4{Dpn#Yu4bdMo*yHJ+R48PM
zzAZ{jL20r#A#8SRQ1p}x`Ujcu>`yAD6#dk>NO1)X4dhN+_>fHrOhqCSQsakhYP&E8
z&f3P+eTgBFN#;Fy$BjwlJ&mCl-T-Z(K$}dVm!{B5Q}`uR#gqmWbf{oJg$Y!cLWLPr
zFrk9QE6iXi@^}d=k|7NWd3-;R3WW^GP{@!Dg$xN%$dD3+3`tSQkQRjuiBZS_P&r|5
z+#jq=9gksUDy&R}m8q~Y6;`Ig%2Zg{7;a?@w=#xX8N;oN;Z_Vd%Yd^CILm;ujPWe2
z$$&LY;F>TW3Jiz}_moP8+EU@3Qen3=GH;<$pv@B@q-zY<Hil~(!?k%yxXO4%s16M#
zMI%G|rop6WFew_mR~o!m8hl*na9cX8Mu*ktaNB?&x-rd|4oOagknThXb!;Mp_An7b
zU7QG^eollCz=;r|I3B`KCc>#^FrJC6Q_UEY%@|X5c;TcSUN~uo7f#yYg_F9xaMBJh
zoYduof6|4{8ED76t<Z@x5khCrL<pTi6Jg*~7&H|I{}I9`!gyWQlpPjiCy>r02eM$|
zrqHn^!`Pv^G-C#Q-crr<jYY&195nuJ2%LJa`cbB06z?~R1oT@4$9p0rED*d0SO5V)
z01yBK00BS%5D-jYmlbj|S{9F=^Pg2^-Dr3O?+1@ZAn-CW$ykI;3L+_C@@JwtFAdfD
zKQ25`1VU8dHy_>#g+#L1VYAsm(d&YujUpnAB6t%{_N;*D7)YE<q^OZq#ZaDTDXg4Z
zBs*qRbdbm5MZ}mOb_|gn%bPM@PBgrp{riy>>C$N<m9iI0hV^^HkkEoCK`i4v3e3j7
z#J3TDN50fq)?r@cB>mnq$nIRh^Xu!QxUT!(q<Lp_rC0l{G4Va-eSW~bX5`s&l-3$!
zt+;ictL<2oL`nd8^B`8?^MxhZ1=AB>nYfF{r0FyUmpi!ZZ<HBpOnK4C%F;8t-+H{o
z^;h=Qm1CzF<}>SuHb2!oR_*wCeJa(Kto^m~qQ`?b-#<IuvrbGlYj%j9*WQ<lti)u9
zM$THXMeOpgYaXRm%ulNN==YjgjywK{&TeA87&EBQRFfXm$79Ok``(w`NDD`EbCMi(
zuG>|;Pygz!wCv`a_p!IkeL7rn6+@QSp3;;;>$+uW&%Gix<XTd)RP60o{f*b@GWF_6
z1d^PDWRUg5qztw>Zu^WwVnvZCvJzUhE;r7q>~2MXJw;X$Ay&EBRyD6v1c^i^AthGq
zD807Z=;d&<>b)y<!}5ea7w2KDjH0j2HpASKvO?^Ghdt68UMkM6?-^d&WSoBL;rxdo
z73t~vck0UC#9Z}~@`+h>dKXvyoo@KA{<nuVptQ5`_@xW6S2NelIEU%f$c%pWTTwZ8
zWh0$wZebVQM4MLGKPa8md{t7Sis9~xuzb`QwTi{UJkJ08R^;6yHw}lwkFJ?YWXfR@
zwO&fRb}jvqFV(QJ|FmkrPMt!zC+gB^C;AV5c-5Rew=#_o;izs=*ko2LYq}vNHNi&i
z!b$et3wM&X;q(^VqJ(~nSzP<MvFEG&@Pfy^{c_egeVs4i3#J!e(f!)OZacU;<&D?g
z{0E<E<BwIx`f|yf^1lGLInpD;HDPsC+SuWSC-3&uvFeYcK;SrOrH9wd-P)gtQtA7U
z-1qkJp|B&@X4bgO<EEE4EnpsqQlpoXZW=^4Sp{7zHT#$@9$fyebj9ds6Nze6>a4BD
z2n^Rm8TGhPUG$?uS{8j04_>TT`DOUb+wIfv{f9;4wbC-R7Pt%zJl}BgN#fnE4?6-b
z4cX<G#hs4y&=fD*SF!0@+;#HiO*u=%|JwKJueSzMaLtLg{Z=RDoceA!it<`C?)#SK
zLY+N_Y@P?v3pTcMx5&LRu1&kpmulB+)X=oa{QAKPxl+BoLm#uctHf;$hc|ycwP$Je
zFI%tlb%mE#r>Xn*`^0GvcOG3<)23lO-y*U8e5syYQ7KiW?6w8{mvgISjkXmYSoSz~
zqrp%}GI#!xr7xb@ymtS1adFDioQ%}Qfd}<|DI5LjJ<Sn+?R)LxJBOAi+zJ(c!rpq0
z6?EE?(_1#~d$toAA^O4oUT5#g+ARBD`U1{}I~LsDU5&_9J-w}NWLU%C#4pw^bidK=
z`%G#51OF1uKk-eei(0k6!{^MxsxG;YzbRqu3(9=<<CaC~#Ex|_Eze6oU45~NC_AjU
z&T=dx-@(6U^lVyY-F2HFeI_2cf#P9!v{Y*_*|V>RMJ3m%Z*AXYF|5h@Yv0nQPVu&H
zMIA5nTrAOwwQsU~<M4LeH+fy(iUJ?I2AR%Xapt#WslMmJ)JkR1Y7x=lXO?V>zoX%z
zTjXYPL}mB3I*qPl9{#eAEc~1A<VDLR2PmAlFTcQcByq#pb6&eP-thJPaPsLH{7ZuE
z>My?)EB-64-o*Q{4cexw!0vk2=+d+ht5{a}_Pf=utTAO~<qR#_z`h=(UWNLF<=t92
zOaIz8%U|~6c8eXyYTA?6Whpy^OS{*d9rx|V@F)6r?P=K2V!6`ONAYCrnKFO&O17zZ
zUH{Q@{t;IbMHCddHBsnS=SPxI{ym;KdaWHxo2^y-zFi6t`NZ&I%gBF;yyBK7dszGS
z^7Du{I-~;o1W9SFZ|P>!cJFcA+xkGy`1~`)6X>d@U&ZOibxwOmq&8cY?hIrENAJ|z
z|2${0=e)J$8#<u-*U10xBfBI7fp>qN_W%nZ00;mAfB+x>2mk_r03ZMe00MvjAOHyb
zj}Z_@p)t_K^YQsV7eV0uk981q2M_=R00BS%5C8-K0YCr{00aO5KmZT`1YiOv6aq6o
z|8GMO+5i+l01yBK00BS%5C8-K0YCr{00aO5KmZW<=Mm6GxnTtV<=goD{}qDp>Yvv`
z;20191ONd*01yBK00BS%5C8-K0YCr{00jO-Kp8a$^Edw=XMFzOjUaUYsXhn+0YCr{
z00aO5KmZT`1ONd*01yBK00BVYA4xy~MZ^5nf4LRq^#(~8h595lzWPUY510l700BS%
z5C8-K0YCr{00aO5KmZT`1OS1*B_OE?-T&9X$|DGaggFEi!bQS*!b8G-f)AdFx52mL
z3-Db075o-l8ZHEn#?|39aj$Skv1hPZxS81ZxCJ;Ywi!#u%44IkOa68zAOQ#f0)PM@
z00;mAfB+x>2mk{A)dbW~)<|~9D!+iB=omjTonjIYOlSIye*rm_s=+r+CeA?3gIk9N
z#ZNXi`=hZszbskpCuur`9>ntjf7QC#48CboReos}U4>to&Qj)=X3>=RrRhvXerX0-
z;Xl)iAX9SSA03nDn>Ll>muAsr`K9Ts>HN|xnhd`*ohi*P%^*+vDQyx+qx{h^0^hVL
zo?n_p$MH+kSy+B)7EOv@n$DEumu8SLKc$V$jH!QgOoDIPRGeR$MHl0jrn5x(rCBsI
zzcihR;+JNSMWj$OFl(7?c3fn1AdWW<DI#to2nF~NyeIA~P6`_=bzMqTG7fVOV<eF#
z{zBYREJt)u)D?XkEsF9NsS=S#M)4Z_7cLx<qNsI}4C058>(Ao{lg!yd_7%fSe21+l
zp6p7to_dKT)6m3w4ae7L{$5h<O01?`SoOL>^~ST&qJ7%w@0Y0_OKT(O^S;AU1W6=I
zib;&Ch+zI4qls}5Ln9X=W)dap<A#aiBNeMOUcA{-EBmP}BVnJ&imVqLk^*0o46+a=
z-_et@XJ4>UT{(TI`9<>a<)23FM&|h%FWr;4efGf85fecsN1Zph95OGeTC&RyKkxp(
z)_r;UT<f}}?n0T&s{G8g>mGAR@;{mUaljJ7q=;Ko^|ZAYrKH!D51`I}NNT|v-Zs!E
z;b>MQ5ss>-R|zs%I}_oXfGF3`nD2TvM_B~Tsi!k+KWHXxoGveWw$db=Lz3ff(o_hO
zAzkGU2X^~Ez7_fv5tsd*xk>kJW7fQ;_}YOD%4eR5Jr`tBu40Eu@8L(M-{68Mx*6I>
z`nT+4bY|t9N7y7)%30>@;E-hbnq<+1F!}uK;FHg3>wC%?qy6!|g*~;1=&O4ea-}0#
z%kMf4j3fv$sr!a&rgzCCV@qq+i}kOP9N&IwzI6U7{<>C$<+;Zr@9`YcbiO9(yzizJ
z_{Q1DDVi>2xFy$nZsH1j7x9?BuI+w}T4lZOn~PGex6d^YWb$n9-r?Dja}HQG5S-N%
zaukn*(i*o>R^%`ON{%GG%;R!MGJH+4XhN9$Hu~a_%BjBfCy&a~4p?j{l;~D|at)iU
z@o~GY)y~<Gc7jZ<NKM`m>S3Htt7PSn_eWNH1l`<%I9dF>AKg7S#KzN`Lz3oek}k{;
zlrrRVdY$>X`1l7$-KOuA*m@}!QQS0`ofleN_be_glqSgJH#_Y1Wha}f%P@OA<PKe^
z`ck^#nk&NT@fFV{(|u>$9^jCs@ii&b5M-!!w14Qa*i4IvY6t({RvjHUSER}vAUDak
z&cP&p=-esDWFFebEp%31;^&qZfww+;1ykuk68;~<hOO4T{C;${QX_{%_}SzSA1#D9
z+^pN!aPu9P6NWoJoKJmee4^&@8I?>W+s2m^<+b;4Y!yK!`{_}4kBh56>o7W1yFGe%
zd+1>P0lVscch8=03+&%zmU2jV{w9SPg1n~1ns?katKU_+vFwf$r*a-8?oPlW$E!!b
z75u8Yw-_hLq@1z~vFtKQEKpOvvsEl%&F%r+!>`AlI^pd1>0CJfA(}(N@ii&b5QNv-
z#Y&jhZ*a!6kd$yRgz?!WF-%_9qMvcIeSb-@5>=4NcIymvZpDIFGUMu~kyT87{gZd~
zm$StTKKO*rMz6kC#UWw&niOgXdK6+8)}qmJx-9heq6Zyk)!tqjF`RV+Rpetl_p4N*
z?`uIO^LH<wX&3NLuh^rki;(3^J>u7aF;}X-`_Z%M)zuc$Ar483uSubXphg?3gIVSq
z#+K_~C8=?q7B^1r+Vv)Ve}$UNNGfxhubUu~^~t^APmFrM-gO+RFO(ZN+&<HEZrdt~
zt#iJFf4AELR}M*%uSubXAQRj6=ZE$X+|2K-{xq{zgXWC1_rCO~lI3~jMR)$W<`F?A
z3vvz*L=}nHVKk<ROn3f$<Kl+iE~h;z?7V49POMzMIfg^R@HHva5agJoWhUKiB0)cW
zoZh;xw~;XD)LW|O7UtTC*86>HgTN0MW4^!0y!)!=a5niN+cstP{i2uLRHNJVO7Wcm
zPgk7|TFN0w{A}`vNq`VT(5p-J#n;V3vk)iyGpC*W-da&o;yW7L8u3LZxu&@%#X%6s
z<^BsdUUKlhTteU$kFs~Q;~UC3YVTWOTFlf-T)Y<0I3#iYCWRS-zLVFMEL@}=7~;HN
zGNv!J_P~%R+bE{_8tYVq1z|LPogkB52g)N(tv`T2b=$P|U8-UGdN*T6QU9po;`MW~
zy;hXva!6u)O$s#xS+^l(4;*q<>)5hX`%-3AbF2TL_EXbB|Ay-}g;G@+y@E`-9%Cz4
zny-7P<6^R^{p^Q3oE%X~x<Q75_wcC)jhk!IIV4fOCWRV;o|YbwtO$I!Y~SWrE(_$>
zWEiFtZE!Sc$SyDY{$x;l!)ie$o9vYn)1o|*11uA?TEBNnoh&V075r_^kn`mRxk}{l
zRt^cx*Q8KGkaNbSSdVST9iOc6m+dy_#BrO}l!dG3_j!lwcW2>MMg^HPKN0puN^$Uu
zvgm5v^*0mdSG>kVM$Opp<o(k$O6WC(?;H|}uSubXpyN;EzIm#*StZcKUbWKm^S%bP
z_Jp~$uRCgaGt|AN-cpdsG+kwtT{k%rnTLqp^JOgVKNQu{a!yw?sh`Jf^~_V-&moEM
zH7V2(RH(m7<jT{hg#l}I!a{NwO$MPu>#x*uy?j2M)it@<ZYap)wR3~odnM&9gTHSN
zSR0x0tN201c?wH?5+bb9DVE$vEI*{^3>rsba<*>RFpG3h^fIDY`=$3`b2IhzZtaW7
zbIE#QQWH~JtSAyiRzk~~K1y^?t+1-GTa<I&U3FB;EHC>F&kK5*z1kMThP0I8u-OLq
zMzzKcX#S6pq$3E+@fY#ZxG-!zR#R%TWE*c2SO5V)01yBK00BS%5C8-KfpG#HlEzOT
zoqxPVEX2h4MjLng3@MqDL*z{#xg%n^#*(cI60UzsdY)gFdL+ME-~~jz%$F<PKij9a
zY~bpH159>gT>Q0O!-CN09B%OHmbX^Q91@YgNnviZ=<S*7M^S5!X;r$w?rv}B_c)@U
zFMTbdX1Vo#E2PRXffu#O^%71G-|CHQ+IS>#ixM~OVa27Dn^%{%q_;djlu~m%g`=S^
zI&b2Z)nDE&7XswY*mjZG%PNwSR~a&w-LmRg71mLMlvR8--vAl8_ObMspg9Pu*U`3T
zmiVVrUuH(H4$mV{f4h4)`;+5|mb&eIxg$Qs91S(T9);o95ngro?&pQSUcg1wm5MXp
z*W?nOd~0iAXZ3d}hTo}qFUVuhaLNmx%9$1lxVPJ170zmZ;x}ElD%iCH{~~@&tIdaZ
z6=cTG2m8;r{>+2`+3MWh9nF=R+e%ecdf&!osq~8A)bA-D%pOI1eXQLnA;_dxblZ&d
z=Z~Lerd~1IcqDcV-y`1d!7Zd|#jfb|?9m?OkW~4b6y{Qm!C9J7pN3=bNH)fzyUgQ4
zZLe9|{oL)6;=Tv+mt$JH1epxDzm2%;Wa9i=kC^YV+9u(|+=E*@9HYu{E8JBIG&Axz
zBo)3Ug}PLe$&pk`uJqTHL1C2VW|@bcwbaDWbG?))st%j<N4n+<GI?N=*`sMIysH`*
zffp-OqTk*sFj5}dYe7*`eOPoOf14_Yq|Dc(P?u^Rym)>8Lz_YZ-m`=ILFYgvDV=3%
zrbyEJSUh*{txt;Sf=nLlF3H<Hw%JH^CoPNlHJhb>Q(*)jI!_Jd^d@EGvR4*|q{P>x
zP?u_M#pm?=Hpe%o&rgy09QRr7iun?}uZHWXp#|RN6*JKL1ev^b`@y0!>g5Xab^J7R
YwJF|(xqDIH>m1rw`)b~lamYjd8+gHb$p8QV
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..78691fd862
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-no-names.crt__server-no-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-no-names.pfx b/src/test/ssl/ssl/nss/server-no-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..3db5c17f9c65ee8e38b435307569945f5230eef0
GIT binary patch
literal 3109
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-password.pfx b/src/test/ssl/ssl/nss/server-password.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..973ffd13c772f74dc2fcdbed87274f78fdbbbffe
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..f396d2216eddb9e8080c5f2a5398a0ad30a330d0
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..a3c30ee146f36500198bc5b7401260affff556e5
GIT binary patch
literal 45056
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
Daqo==
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..c5e90c7000
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-revoked.crt__server-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-revoked.pfx b/src/test/ssl/ssl/nss/server-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..52fd4217213867aa1dcdd8d0bbe428dbc54c7b7f
GIT binary patch
literal 3181
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
T0tf^MS6bnOG&(z40s;sCEb;V7
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3105d1ccda3f613c65ac25c887fd054226aecbb7
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..ab1762634ccb5a0b9a25ed5606495032285f73e8
GIT binary patch
literal 45056
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
Zzq(1rW?-`iW_zVhN4x$u9TNuXe*hRnL686d
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
new file mode 100644
index 0000000000..82255d3f4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.pfx b/src/test/ssl/ssl/nss/server-single-alt-name.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..3d0fee97935a0513dbaa0979b6754b885a6a7c71
GIT binary patch
literal 3213
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server.crl b/src/test/ssl/ssl/nss/server.crl
new file mode 100644
index 0000000000000000000000000000000000000000..769196dda6dfffeb4141ec007d6e276d3afb5e84
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sVYt_DoZU=NKP#(DHi87v@kR@GB-3fF)%fa
z66ZBBGcYkUfpQH*41|~%+0YcBIfJ>0k&z)cx-<6WM&pI2r!*dm3egYCR+DZEcFbs=
zJLCSW&CW;U#5aBOwXHll$=p%k!bPiXhut}MT~u<8-g_X&?EdN?ab}a1JABr;+ig`=
z<67*aH7|L#+SOR4x3|MWJEoZ4vY)6nxx9MOgT+t(bDezlrzuZUzqqQ}z2kL-Q>O0U
zHbE<wjSIh&+P2+I(flFf&)N8NZ@bU8AI59GGNp869L-9#II(HANW;al9|>u9KQFgj
zG1J5K+BS|et`!}!yF)I0+pZ_l{^ZHDOBr?gx7Y$1Zp_zSS614#>}BGLu8NhBoGu3M
o9tXbUKYKSkd*T+M_r-Y*A{D<U7Q|28yuIn;%#d&L*G&^z0V1ELsQ>@~
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..0c8a303d1121dc443d26281cf1c9d688ff3613ea
GIT binary patch
literal 28672
zcmeI43vd%f7{~8&NlIGU6s<-m*b@{R2FhOUlC%y&9|T${l)iup7}E5hv9+;DDh!I0
zj8IfS%S%B~5fl)q$Ws&;gaJXFD$IytMXFVK6fIO#1Y6wQT+)_N2M5MM{cdJA|9$=U
z+uv<6-6TzJcBacG^Ap{k2OK_LWTF|CWs-TGVHgglFr0#y1`BntfFId*`p+cCB%r$@
z_2(IFcq`Lcr{5c~FQPIc85iIJ0zd!=00AHX1b_e#00RFwfn=@LV2EQ2TxCV_6laOU
zRaEBnj;tswbveV*a%?GiHa=s-VA~j;w((gb_)t3^?+}p5I8Z|!@~K0qqfjml_0V>3
zplz2sh{nNctsyp!9Z$!5D+=$IoxYHRrh|jhdbx*y1Y-)PHFOQG!{zl>$exgix`Roe
z@p6wu!B~~Vh8g1G*m)^FM`5WP8XgiV#V0Q%HPhCfgR6)inUgU*C8vNNW-H+HM`UE@
z+sF)QStEvIW~AluDXCdGd3|{Y!b!l%gp)`#tspf5sY)cpL{dzoMbafvBAJ<F7Lr*>
zmPoQBk~v6Lh_fVWg#tNsM8rctfq96FfQXHNh>w7Xk${MkfQXfVh?jtfnSh)mSHcU#
zN1aUpO`T2D*+iX9)Y(LxP1M;$okcoIq@zSSN~EJiI?6&@Ewt4_TP?KJ5@@BK7V2rG
zJt-f70y5E+nh+UlqANAgXeER%lR&B!Nqj}xTco{3+8a00E`g4uk3>mHh|DcfQW7O4
z(OD%rt3>aMnT|D6H#2oJ)3Hw3EJ~u8SSphEDv~Trkz@u%k|ipVtW%K$pdtyyfTU0q
z**1*gQEJ<US%SkXZQ)=U3<t|#I9LY5!7}KJWiT8pgT7d{^Cf$ROpI&E#!)2MJ&Gh-
zNRdTtQf`wILy~TUK;Oc)a0*E{MP>&oDx|~{$-PAsJLxNl7P@auNk&1XHQ3@<Ch#Is
zUW!ljc1MNJP0RMsGuSSKBstr6A}_a*IH|(7FcI5u1{V+j0zd!=00AHX1b_e#xGo9!
z8PybZOjOhjB)S>V^x?__jk@x#xMvK$$bcxRFL}^tnt!X!P0MAOSD1TIvM>-0&~o`|
zuCqB$_WF2_+wJ4iQpg8t{7ARgH_0RCW@qxbxtY92#s#m}<u2oWq^q|A|DCR4Sr*&n
zv`@_5Vt;wvs`;ONA9bXEW8D17-!8xJ^pIV*S6A+>d9Z%i^f|p|skh7?%uXc;dNXqp
zn3#?XX2eJ&)W5v`9)!!RFocV^3V&4Sl|2v19=p?#;Pm*ApNm9(_NSLP6|2H4Ly=lt
zpIPa%eu-q+2n}=fw9u`FNPGYmOjbd}hg9iaL!@Luk{~HXlqw{nfr0yBrT60sZv|Zi
z@02}0FJ3^;_JOk+hV!#}2G@tH{CLS<-}6w{vPpOEe0p7<?@Nt#xpCbA-Fr1<H=;Ae
z=2JG`%yP~2bM?0*i1T|#cK@<9cHS8M#`_N?r>>nmbZe<zSNnYP!uVb_Jsw^9$?9MG
z*H&#F)2+bVSd^V)In**>&ZpHSn~&NyWjBUx-uTIo)}<%UJZxF&IMt>3hh9hA!|&!k
z`ZjaP+4M78e{Ooeb<e4Jn+u9nwu{W^)qPWCb3Zo4J*MDbi}jg)F58E(Yle+>R?hoq
z@!<n&j2F^Z?y5YTyKiAeSyQ^_g~u-~Q}<W5Ud%XWlt)Wb_n4cLy1u$|SoABZJ*TJk
zThaHm2}4(EA|AQ(*uH6N4>ho3{pa(SzjU;0X4UGNJJog-$1?1SXcUDyW70itRBI5&
zvX|O3!#5i<W0`_+M$5vMbZc6A$rmkoX{$!<ujw{wddbGwP5Ec#gX_91TVK5U<t-7X
zKhC#3JAD6%c;Dbr*0;Y4?{#wA!LT*)myT4XtvE7h|G{my*uzV{+j0A#C6gu$lB(ZK
zpC)!vKliC+`{7gXzJ9z*R(Hpe4^~d8KKDaT(}l%j4a?^~S-zk;wtks(a`qjLv&Uy`
zvi1J4?uj*a{;QrlUZ}rqbW`y+bqPsxKToZF(JJp=wC}0z6YA@BOuVUf=7p2bnETs)
zeQn2BZgxdocF!L6hMDrxwT=Gp*s78&|6AIl>)&4R*nvm8-n`c^{#cd&4QqyLQA6ir
zi{78uEp_)D%XUY9u)SqM(l%yvg(ki>n>=l<e%F&{(lyxF?<{C>H-DUKH;*&KJh-1<
zvFgM$h7HFNs`O2a{yfg$0s=q)2mk>f00e*l5C8%|00;m9AOHmZ3j{iGYL>i%sS4xN
zI`S@F;QU|C=(p+1{|gI*H2?u100e*l5C8%|00;m9AOHk_01&t?2n<!vh-R<BcLIiB
zAKlga1kV2(82twQ*6V@~1OWtq01yBIKmZ5;0U!VbfB+Bx0zd!=U;=86VO8ns88NzR
z@lOGP^Z#*1-=sedc!2;A00KY&2mk>f00e*l5C8%|00;nqe>H(H?gkeBUWqE2>#ghX
zlO2Kce*>d$(4Y8MuMmO<0zd!=00AHX1b_e#00KY&2mk>f@c$-2zc66nB;6nV0O0TQ
CAa^7H
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..7724801ab7b783dee9780314a10c797f4b0bc203
GIT binary patch
literal 36864
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
bOBt^0;uYb_MnN8~Y(fz+a(z_rCkp=p>@<Ih
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..b81ced09e6
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..00530d98af 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+}
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
OK, this version contains pre-generated nss files, and passes a full
buildfarm run including the ssl test module, with both openssl and NSS.
That should keep the cfbot happy :-)Turns out the CFBot doesn't like the binary diffs. They are included in this
version too but we should probably drop them again it seems.
I did ask Thomas about this, he was going to try to fix it. In
principle we should want it to accept binary diffs exactly for this
sort of thing.
The attached v9 contains mostly a first stab at getting some documentation
going, it's far from completed but I'd rather share more frequently to not have
local trees deviate too much in case you've had time to hack as well. I had a
few documentation tweaks in the code too, but no real functionality change for
now.The 0001 patch isn't strictly necessary but it seems reasonable to address the
various ways OpenSSL was spelled out in the docs while at updating the SSL
portions. It essentially ensures that markup around OpenSSL and SSL is used
consistently. I didn't address the linelengths being too long in this patch to
make review easier instead.
I'll take a look.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, Sep 03, 2020 at 03:26:03PM -0400, Andrew Dunstan wrote:
The 0001 patch isn't strictly necessary but it seems reasonable to address the
various ways OpenSSL was spelled out in the docs while at updating the SSL
portions. It essentially ensures that markup around OpenSSL and SSL is used
consistently. I didn't address the linelengths being too long in this patch to
make review easier instead.I'll take a look.
Adding a <productname> markup around OpenSSL in the docs makes things
consistent. +1.
--
Michael
On Fri, Sep 04, 2020 at 10:23:34AM +0900, Michael Paquier wrote:
Adding a <productname> markup around OpenSSL in the docs makes things
consistent. +1.
I have looked at 0001, and applied it after fixing the line length
(thanks for not doing it to ease my lookup), and I found one extra
place in need of fix. Patch 0002 is failing to apply.
--
Michael
On 17 Sep 2020, at 09:41, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Sep 04, 2020 at 10:23:34AM +0900, Michael Paquier wrote:
Adding a <productname> markup around OpenSSL in the docs makes things
consistent. +1.I have looked at 0001, and applied it after fixing the line length
(thanks for not doing it to ease my lookup), and I found one extra
place in need of fix.
Thanks!
Patch 0002 is failing to apply.
Attached is a v10 rebased to apply on top of HEAD.
cheers ./daniel
Attachments:
0001-Support-for-NSS-as-a-TLS-backend-v10.patchapplication/octet-stream; name=0001-Support-for-NSS-as-a-TLS-backend-v10.patch; x-unix-mode=0644Download
From b397b75d49db00ef58e0cc4a3a456aa7d68d06fb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 29 Aug 2020 01:24:10 +0200
Subject: [PATCH] Support for NSS as a TLS backend v10
Daniel Gustafsson, Andrew Dunstan
---
configure | 211 ++++
configure.ac | 30 +
contrib/Makefile | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/acronyms.sgml | 43 +
doc/src/sgml/config.sgml | 21 +-
doc/src/sgml/installation.sgml | 30 +-
doc/src/sgml/libpq.sgml | 25 +-
doc/src/sgml/runtime.sgml | 78 +-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1038 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 +++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 984 ++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-encrypted-pem.pfx | Bin 0 -> 3149 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client-revoked.pfx | Bin 0 -> 3149 bytes
src/test/ssl/ssl/nss/client.crl | Bin 0 -> 418 bytes
...ient.crt__client-encrypted-pem.key.db.pass | 1 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../nss/client.crt__client.key.db/cert9.db | Bin 0 -> 36864 bytes
.../ssl/nss/client.crt__client.key.db/key4.db | Bin 0 -> 45056 bytes
.../nss/client.crt__client.key.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/client.pfx | Bin 0 -> 3149 bytes
.../ssl/ssl/nss/client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/client_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root+client.crl | Bin 0 -> 393 bytes
.../ssl/nss/root+client_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+client_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+client_ca.crt.db/pkcs11.txt | 5 +
.../ssl/nss/root+server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
.../ssl/ssl/nss/root+server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/nss/root+server_ca.crt.db/pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 28672 bytes
.../root+server_ca.crt__server.crl.db/key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/root.crl | Bin 0 -> 393 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-cn-and-alt-names.pfx | Bin 0 -> 3349 bytes
.../cert9.db | Bin 0 -> 28672 bytes
.../key4.db | Bin 0 -> 36864 bytes
.../pkcs11.txt | 5 +
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-cn-only.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-multiple-alt-names.pfx | Bin 0 -> 3325 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-no-names.pfx | Bin 0 -> 3109 bytes
src/test/ssl/ssl/nss/server-password.pfx | Bin 0 -> 3197 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
src/test/ssl/ssl/nss/server-revoked.pfx | Bin 0 -> 3181 bytes
.../cert9.db | Bin 0 -> 36864 bytes
.../key4.db | Bin 0 -> 45056 bytes
.../pkcs11.txt | 5 +
.../ssl/ssl/nss/server-single-alt-name.pfx | Bin 0 -> 3213 bytes
src/test/ssl/ssl/nss/server.crl | Bin 0 -> 418 bytes
.../ssl/ssl/nss/server_ca.crt.db/cert9.db | Bin 0 -> 28672 bytes
src/test/ssl/ssl/nss/server_ca.crt.db/key4.db | Bin 0 -> 36864 bytes
.../ssl/ssl/nss/server_ca.crt.db/pkcs11.txt | 5 +
src/test/ssl/t/001_ssltests.pl | 289 ++---
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
106 files changed, 3479 insertions(+), 266 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-encrypted-pem.pfx
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/client.crl
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/client.pfx
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+client.crl
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/root.crl
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-cn-only.pfx
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-no-names.pfx
create mode 100644 src/test/ssl/ssl/nss/server-password.pfx
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-revoked.pfx
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
create mode 100644 src/test/ssl/ssl/nss/server-single-alt-name.pfx
create mode 100644 src/test/ssl/ssl/nss/server.crl
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
create mode 100644 src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index 19a3cd09a0..55ba9526df 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -857,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1559,6 +1561,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8106,6 +8109,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12180,6 +12218,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12442,6 +12483,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13344,6 +13536,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index 6b9d0487a8..970bebb24a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1235,6 +1247,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1410,6 +1435,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index c8d2a16273..57fa998526 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -51,7 +51,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 84bc0ee381..c3693283d2 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8919,7 +8919,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 2c75876e32..3859fb39f1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index b585f22408..60cd6ff8de 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -969,6 +969,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -985,6 +1010,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b50391caee..cd8e170938 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2497,9 +2497,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2526,6 +2531,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7958,6 +7967,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7984,6 +7998,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f584231935..bfc8218c7b 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2377,7 +2400,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2424,6 +2447,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2551,6 +2582,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..8946fa696a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 36565df4fc..ea6d97585e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..fe6540852b
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1038 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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()))));
+
+ /*
+ * 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.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * 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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback */ );
+ if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 596bcb7b84..ce6200c851 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..f11ddd6b2e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..ef5f105afc 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 724076a310..0acf521624 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..778393fc3b
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,984 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout pass:
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +233,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..e8d1510529438b26c31e0880163aa391bd1870d0
GIT binary patch
literal 36864
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
Lp@HYkDZu{;Ke*53
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..44e6fcf5307a8b2e6cbf9878b157d7cb3e3a3f25
GIT binary patch
literal 36864
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
HZxsFqjRv_Z
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..dfffe92b90
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client+client_ca.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client+client_ca.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-encrypted-pem.pfx b/src/test/ssl/ssl/nss/client-encrypted-pem.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..4472d88ab737eb98c25d0e5fdc5124533529089d
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3cbd561f8a1ebf20905080adb814e79588c7ef49
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2a094273f3761c4c1ecc6a59b28f1147d13b7736
GIT binary patch
literal 36864
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
XbeyC#gN2opHif0+vD$=@ZxsFym1*+x
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..67ce598fd3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client-revoked.crt__client-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client-revoked.crt__client-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client-revoked.pfx b/src/test/ssl/ssl/nss/client-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..1ddd9ff2823a5625b32e538ae91d029df841bfeb
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crl b/src/test/ssl/ssl/nss/client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..ec061fd17bd11aad4eea2cb4980cdd9620be5f9e
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sY=etOw9vQsYNBl;=G0yhK5GwhQ=larlwKi
zye4J_CWa<Zu7QYw5ECO4nj$o3FgGzWGQ=5uWWFNvib1cOm7QIfWA#LK*3-R$b#Zr+
zQzt9Of9Pf2QgXg#^7|Q#`?^;~od5q&E_3mbwX*KnbDH;uHx}8yc;&n*Cg`u>*0_IV
z2Y54&+qGrPUC4dt$>ZO*)%q{4x14vAo8!GyW3<{9)zs`N>D*O<h4!Wm&zYD%)*XAW
z?8h?G^mEIM=O3^Yk(m1JZpp$KRu`9gue$prUuR!%Rb1VVhy91s9M8Qw^)xx^n<TUM
zv3Uho7B_OK8=h5cdh)~l<(Z{@Hw(mAHc$Slv&lrgXD46g|EyT1`^j4q@6F3L%d)b)
nrK%&VUs_%AV!{6+)9&7a`}PORtmIRl{k+g!;Q6Fz-w|N|QYfe5
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
new file mode 100644
index 0000000000..3ac5759692
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db.pass
@@ -0,0 +1 @@
+dUmmyP^#+
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..969d719dcc131dba358d25d9bcc6ba2c5db0ff6b
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..bc2f47f7a1ef6cb4c6b0b2d0ab69cec35ad4a4f4
GIT binary patch
literal 45056
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
SsQ9RQhx^7M%g+|xF#bPn-f=kq
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
new file mode 100644
index 0000000000..190f880c0a
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client-encrypted-pem.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client-encrypted-pem.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6c8ec171304048a2a1c7aa14965b30cb7bea2ec8
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db b/src/test/ssl/ssl/nss/client.crt__client.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..73cc753ff42454b2d6333afa7fe3470ef78d4d9e
GIT binary patch
literal 45056
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
Zvrc*RxPLw*z1dFh*QOKE^1i+;{|DZOS-t=O
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
new file mode 100644
index 0000000000..3f0a9ff5b5
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client.crt__client.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client.crt__client.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/client.pfx b/src/test/ssl/ssl/nss/client.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..9c5f5bb3e5ce60d158e87d7b9b114f29991a7d32
GIT binary patch
literal 3149
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..710f74aae2716883ec8e8bc4f8490b5f37063abd
GIT binary patch
literal 28672
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
F@Oy<<VN?JB
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..13d47f000664f2003064ae9110f2f5a07013494d
GIT binary patch
literal 36864
zcmeI5du&rx9LIb6-rCXekTS;DU57$pz`4D>UB{--mGTICmhluRw`+mQU~KIife2KP
z@z{Vcc?A~0zziq>V!}MYaPD2Sk_QNth;kw*q7qVb;7TMHp{``7Wi$vyp^^E>Bz
zf9G>f?{>Rn9bK5?3wY_NrR6jE0L@C`B@`tYLemn7L?)au!Wll4f{=$R!YlQJex8&`
z2B1N4x<4f9Sf@lQ*PYZH(^P7>*t@aL*yGO|1noco2mk>f00e*l5C8)ICjuoZl|Ctn
z3ib=|?h@}zZ-5`l2l()-+U|7NiX3#2Ej!0Shg<1BJ`Y{s9GPo#PM}9QCeUN@Mi!27
z(D`|^JwI<)&PaO^?Q|66*zArpTKIi+CKGxw83(GBdV3Nj@y+sh=lIJ?grJMB2$Yh#
zE9~!L!YU?PsnqLJsMkq{;u*ZpGt2KUDEGa^2fQjVxsiE89ph;d#3@8N(0zCYnT$ge
z#A6)sDB<1Sl1LZjQywJvj0bBRCRgecQz%y`?F#ps-r_*SL-CY{KNNn(g+YwbE>r3g
z!n5)D{T1Hwh>iRyn^5!_7b|0|%A!>IloV>Eoh(_zAflwARcP5F?QK~&TXw#)D2?Wk
za0=_jn}n0anl#`R1~*x8)rzZDQpH`YW?XQ%u;3yc7gk(k;DX16TPQM!6$+Ka5n&Go
zg|35L7=&#Ygnbx<jTnTT7=*1DguNJq%@|aSOEJAre#F@n(!|+BoK3{pM4U~;*+iU8
z#F-_dSTc$wqgXPEC8I1PY9UbziCRe15{eQ}3-L@RJqaI%05Xx8nh+jqA~Q9SY|ThG
zObl)oCH7@WZ<h3CNpB%Yx`aC7K4wD7jPSM1gp`?(GLx&C$yLo{T{tq9BW@gV<H*=z
zFUOi$4qJ*6`-&1zOqBQvqQp}aC7!1!F+fpbiXlm$h%z#a;1OFR!z|%pmPk5+45uT=
za5{nvrz6O)uOP$e2r}#|$R~X9p1~IuTJgpaCEh)v#9K&|o{02D<kbHp*$AP&?npXr
zoQ{X%k;hGlTk+Z=f*toYvlg;%O&La3s?-lpdEPBB@?Lc@5sjbi)%d4M94_K<0S6EO
z0zd!=00AHX1b_e#00KY&2mk>f&|V3|;)%iYe|ueCm=_QL0zd!=00AHX1b_e#00KY&
z2mk>>0G|J0BLD#)00e*l5C8%|00;m9AOHk_01#;Z1mO9<{jV|15C{MPAOHk_01yBI
zKmZ5;0U!VbfB-!I!yW(vKmZ5;0U!VbfB+Bx0zd!=00AJ-{t3YIfBRo!m?01V0zd!=
z00AHX1b_e#00KY&2mk^6|NqsR9unP_4v#vFiQ5~e(N5Kz5E|eB0zd!=00AHX1b_e#
z00J)<fuKRHu)Whs;`dE+6?@AAF2o_u;wff@8u{w43=~|5CMje3E*Y`pVH~CCfbx+o
zQq>}}oZeZTRgm)Sx!H?P6>dD(dHVhb2|El&`(FHbsP@6#9oJfVODRf9(I{4_4!KBU
zp44*s?X-jE>Q{fX_&b|6Z>di=KYrP(4V|=!Uv=ikPgY<3ep%3<ik_sk4M}G9Q|BH%
zbf`z|Qf5WwR`1OVOLs1=8~^C$jf8b|^L0~lTT4>fapjHtl8P&3duH9&4L&*QcIvj6
z%vbJK*QHi>TT!BI3L2DAlXP=!Nb-D5)tLvL-&M_X{_5VOqDB-sYF?Xr>#WpgTxTz=
z-*Bb1B=^N<H>9trdbj1DmnRMU{KNB!>lMo`oalC0vZsD~{ifS-L4zV{lAOB@NltVb
z-wW<tU$=Jp#?&eIt1mrln7lQ!@kn#_t=LXYvK)15Np?;9=HO52(UW>KPi-FeE3%B$
z9w@j|=qcPhzEQ=LW@iQs@~BC=&230h`b#el8<$FFxi;_#tG6|_9Pi)bZ;j`TiuWtO
z9yK7XuC*l3%&4!ipWA(^W1Qkg{h*A^f6lA@Aa(fM>_w0F)FfW&KR9TRMNLxpR=d{U
zILE4vB#*tCoHwzu>E1%^#0ILNcEPT*o38IW)1NJCyx%IxrooNe^jovHHupLFV1P4s
zjl5y`+9B?S+Vt+8w5x|sp9vbIQIoWwHY8b{wLLk;&}Dbo;V&-icirw=V12x6b1-{v
zvhH|U@uGgx)<~Y7_~qP3J$E|?&b`LmInZfwdHm0-jaycw|Ms}6^N`}x-Y!a!WDcrl
z^L~G2X}QNWEl{wgXYU0&bB&c1_b%S<+-347C=2ybYQ$TRRzb;7g1k%8ie60%=dF{z
zbYp+1qGi?cllQL=7IKltf41Y^<ru0%V=q>-WE1+~Rs8pVwJuSjYtaqTCF=I;=IAcz
z*6Sv`V7GdhE)W0$KmZ5;0U!VbfB+Bx0zd!=yto8<%52p0Kl)Qg4O>;w!fs9(EzG$U
Z(ZX)CJX)CJWzoVGB-O~0V#1#&{0kpicC`Qi
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..212e72edeb
--- /dev/null
+++ b/src/test/ssl/ssl/nss/client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+client.crl b/src/test/ssl/ssl/nss/root+client.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..480e281015f26cae72cf86ba29e62a4fac1288bf
GIT binary patch
literal 28672
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
J@B0J5Zvjw2gWLcB
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+client_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..65f7eb7f0b7bf0b581fab253b06484fe09083650
GIT binary patch
literal 36864
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
a9j)v#tD=<!Q5mglK?=PxAtwA2g?|BX=!#$f
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..bdbedc732b
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+client_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+client_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..cdec09a0dbe8b127c8725dc180b281a355da3530
GIT binary patch
literal 28672
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
Jzv~YGzX1y<hUfqQ
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..d7704c2b80a3a607f026118c205c48afbb98bfe8
GIT binary patch
literal 36864
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
Y7CB|8GB1RODw|N4E<7PJ@Dqi90gcpt>i_@%
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..5d04ea23ce
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..433b1b91c6a7891f52cdcd3f87655486798c5b99
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..065152e027a94fc9ba48159ed8cfa0fc22a131c9
GIT binary patch
literal 36864
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
Y$_`Eus>}=WP-P<u)5v2Y0zXms7q(k}82|tP
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..9e34f7d8ea
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__root+server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__root+server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..dba82085f965c11ae464328fb65e8017f3b8199a
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..de0f7b37cc0bb1e6b656089aea58a8916d64b3f1
GIT binary patch
literal 36864
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
WTcD$bMM)DaY(Z*+#vB{|MB!gEICTvG
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
new file mode 100644
index 0000000000..b54121fc4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/root+server_ca.crt__server.crl.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/root+server_ca.crt__server.crl.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/root.crl b/src/test/ssl/ssl/nss/root.crl
new file mode 100644
index 0000000000000000000000000000000000000000..1d345a098f4488bc747deaa0ef788d7168beb7c7
GIT binary patch
literal 393
zcmXqLVr(_YH{fOC)N1o+`_9YA$j!=N;9zKHV8g~7%EHWJ8j@OEqEM8dU!vgbsF0Rl
zq!5r_T#{at8XV}O5FG5IP?QSf6c=aa=P8tclopp}mZXaF8d?|{8krj!n;4jyMv3#9
zm>HNDnn1Z|)-g9RGBVUyDtwyVe{1D7zqPZ^SxI)Sy>~!W<pJaJ+>*6S@zp;+c5W9+
z^Lod^S|Z)bS@}Ni`Tpa})~8e$KbpomWoGw<j^>Bllh2*f__60v-OkD9uJ?Uw<TTk{
zmS=us-bBmDYa3p6Z@O9bv+~Z)sT{tk*KH0LCdCP#6Le5|<94^kM*Ttl{XHHp&#Ok?
zW?0u_DpK*4O>)g*jb;m5?;Xo@d|q+aiUfUo7iT?{_rZm4@7YV92Ulqo$lkcVF*#r2
zXz>Ca$%>3A*Vis^H2dH0V&i6PQTN~OPHL)m2G=s5-}_AU9$7i>`SIuP^gnIp_kxXH
dbZjc_b^6{}Z@>M}ghR)eg7;aw?eLl=1OPoRrTqW^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..6bccb020a17e1825bfc26b0de2213c63422bfe8c
GIT binary patch
literal 36864
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
SdnmMpl~!>q(ZQF?u>BuKN`Sfm
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..2d3a91e75b37e4fc925ee9cdf4053fe8fbd9a7ef
GIT binary patch
literal 45056
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
Q{jqP?6(3YvVN%olKUb%Z00000
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..bdd10448f3
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx b/src/test/ssl/ssl/nss/server-cn-and-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..b75c8b1df5d958af6e508211fd2f29534fff4d21
GIT binary patch
literal 3349
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..4ad867c6d8c55be766ae060df33685bea252b12e
GIT binary patch
literal 28672
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..b49070fb6791118876f04e1bb84a73c4165c7507
GIT binary patch
literal 36864
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
KwMfICDEtdt8Pm1^
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1e4d3f5fef
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-cn-only.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-cn-only.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..b0f30f7506ed349ff8119b5029a8818cbb04b01e
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..54b86a5ec169812a42836dff088af74ac922aaa2
GIT binary patch
literal 45056
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
Cb&rYw
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
new file mode 100644
index 0000000000..142748c1c9
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-cn-only.crt__server-password.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-cn-only.crt__server-password.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-cn-only.pfx b/src/test/ssl/ssl/nss/server-cn-only.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..ba5bdfaeda2e17f36e42fe551cce074910c12a62
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..9a302235cfb80c46a1bfc408e43e5a99205fc250
GIT binary patch
literal 36864
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
Ra62=tdSAS)x3Qx2e*j|bcw7Jg
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..8907e03577f91c6303d4b523aa88a3d23eb6f48c
GIT binary patch
literal 45056
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..1d08f78285
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx b/src/test/ssl/ssl/nss/server-multiple-alt-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..f90123e979d9373e3057b4cc47ee276b1ed4304a
GIT binary patch
literal 3325
zcmV<Z3<C2of(-cr0Ru3C48H~mDuzgg_YDCD0ic2mumpk(tT2KMs4#*DUj_*(hDe6@
z4FLxRpn?ZbFoFk60s#Opf(Jzg2`Yw2hW8Bt2LUh~1_~;MNQU<f0So~KFb)I=t{`-H
z8*XiW0s;sCfPx1YV_}%kRe+$iw~4lIPqC$hjci8HAzK7H#6yn>2+QJ`x@3uG&Jz#^
zK}t3w4+nAz%960=LoP(sXq{R5$PQ|-!*?DAKU<;4F=iM4*KNE`^F|dilpMXXxZbyE
zc~844xi)#?|MN0gC7Qt~81TB<Z<zY9rV?ALEAl}7Q4I6ji-=v9HNU}>mA{gW@3768
z2HOd*ryg1lk?v1z*Xzf0n)S>avrqmdyIymXBGL;?_mfz$z<p{3XkwE3-U(N9**f#Q
z)i=L2W{TG1i&O__Umn2+$`Ifc`O9O+T*h0kwj|y5yN`Pbk_i<9Hn=h7^Cn&k`ay5T
zHn`wVCs0l3f%tO4A{306x9}4Ue~@3+NZU7Cn>ah*HRQI^i?rRYI`~?8sDwP(Kc!Bo
z8ipiB#uN$1?&B4$fox~&HN|4+k=s<}JG)R6QSW8Bzb-Qsnk7A~?bwcupr6nsZcP>j
zg2BA)>p&y*h-jAlhfg@FT+-(Oh|ZaFV{wFah(eHYp<W2*<b)PvR=L?o-Vaqx(&B&g
zZ{)b*N!=o1HhN!mdn{jYCu50JghH{$x}n0i-KS>}@z#rWZ8FO97t&oXs1+WG4z0<2
z;X_UO6%_b8T7>25mWO`GtG7M%5}hbxDhDL)eU>vsZ3UqVMGgX~OuJ4XXPLs7EB!;W
z=6ZnCqO)+(!+KgpRHpe+8tm2fXuCtM*klr~gs-}^m@Z(LkuhQ-PEkG_)s;Tcc|fI@
zrr&9%1BB~}sN6R^w4d|mh@yW3y@&BBGaBE81BTq`Xp(H3YEt<nc=0H-j1Bey<WjrL
z#>?zU&1h~!D*(q#6f`gSj*EG++f|`p9$K4snZJ$>y)7dsDip5kFHs!W=W@{wZ3*KP
zd*rXzy+xZBeJqw-3r4TCf|-V17iEYlxMw2dUhi`A>lGymH)_J>wUbg5?gKm{7$eMO
zKP@iV|73;mB6>c^l7?_*D|gCLY191REj7=m8Z)NaE;H~BUWqEJIhY9|r3N3#KDpV7
zC8bJ$Hcpgt7&)E;vozVp`?Vb;>B^mFDZB1TJj}Rm@QU?F;Lh#!#{;Le@RBV{y>z%L
z<IzSrH`aV=O6ffFLvJ`FkvC9P8FG(qWM{B0dh*KSSrvddu>gW3=G}cfJC!!g8g0f-
zIJ~-)%R3oDnFXL(0FJ*cBE+I`VrFBE2L@IKz{lC5YzR;ay72=0yyNVg${yPwhyuos
znY)?R1CiF*bh?1X$N~<}AD-^ghzCn!O~t0)g+2b>S)1wv;MPE0|Dg@FlCzu*<Mhv9
z&shnsxVC%hOU$5?P;Z#_>&pGaFUv53le9cmErHI215F|P2Cdb(EeS+cu9WcXNI$=Q
zRl@uy)Z<4D8|z%eL2#B}<Ejv|sa-*ADI~RPM;lazdL-tXKS;Pz4Xub4u;PVA@54@m
zaxIg4Wvb27n0Afv@(@{Ji96h4P%?){c<}1t{igOWP754@spzq;cm2bzo=miba~sIv
z@pr)&M8Azf(sK%Yz(TjMGavQdY!EmepM2O40sA-FG>R$qA~*2kbME~t<haiH7~Wd<
zBLRW5J2WC#WAd5^{z~|~B`!w|WmASzR*kvW77CcW6%Pky+f^0(!=4Pz<uk*x>D;Z$
z$H)#+Owv&%Oj&GOM$SkRv5EkY4IZ*-`xOrVS#pRgTWf}mD^YGT>H^l#qK-lQvmar@
z6?w#rw?qcS>OpRb(RVEW6;AfxYVu4wxc0yr+m0d2rJYba=p(RJXhgT~!q;Va!VL+d
zw!!o3{<$+LpmN8m+(?zvhZwxTY~V}4hIu?;8tmydj|$A<rZ2XK)I+P2w6R9*gn18&
zS&UCS5L-(STz{BsUskxYA@G3p3v1CK#?GAndy7`Sac2nC30;`4K(A<fO2WiI@Q-5I
z-8m=k$rRwN`Vs!T9}!$xTZ1=^)^ZQpHkCxdq7t#jTmpmcH)jow(o81agaeeS@LsPZ
z6h0**fInU0ZUhjo{SKeDe^eQit>Zk5*ICG8Z4vZkb~{Gi838r)eDd&6!8Hh>+LsX2
zm<mIf#RsfPOx1@)Ih|Ni#MO)beBfz7w*b~CFpA$ofC5=S$1)VdA9o-^2f&4PIUMh~
zKeel2H7AZ9`dBVv19Z!%GK=0Gh4sFx>d#HAyjtS(d?g@l1vclg(cgIm((ii!n&@i^
z`k}h0Wq2mYIlXE2*9~V(t}6WOqHZbzrUQUrJtO5#!i1G0X9wfQ@SD!wn>&*?swlj3
zMFB}qUYkfNlhM}k<G&ia8Eb^0rJb#TJBP4`>CBxx4-~1lDxkzFEt`MC)>>SjiG+i<
zvDFZuw&BpJg7{fWt0t-;i9AzXZUv>sQ|1lAsQvc7qq?!MX#!DW;`k>Uc+AW8-A_0@
zi}#wd@88}>FoFd^1_>&LNQU<f0S5t~f(0@Jf(0%xf(0rtf(0f93o3?4hW8Bt3<?1P
zpn?SMFoFc?FdPO7Duzgg_YDCI0Ru1&1PFI+k{fJMg>nJ{2ml0v1jyOV)3_-FlWs8J
zCYBhomm=ImS4V1hdLHo$5cVw@ej&@l^agP&_$HJqim2ghu#RR1*9K|SB-u=ft+-}@
z#(<}g0a*f~ROi{|!NnE{U#5<=(cst^TB*PHTz`_!4_BUoGVXn4L7mMaS-d7VMEK+C
z1<|<Kz^7r2*bqv$jn5vRYK7_K@}K1ffzcI9%37|FZUGyhytL7w<^D8ILk{ET656;_
z_9davhdJ{$qyK3?aH6FN=0&A+#AiK-7<lP-#6r?bk~Zrp?FJ=DHT5J(k`=IfaM*2S
z1#Z^J056+-Js6fGe}1c>%J!7CQ9nUPu5x)Ddt#DheA^UkpTxyta?$|(-8E6>jxj_O
z=eZJBK3e&wrFEt<yD;frd2qz1seiVEIVy;o+x9GR_xCtO36mXZ2yVgBX*crwgP(V)
zJvscX(9|)ZQfDiYSGF{!+IMjk_loULHrP%|aOuo`-#An{4$PMMHC~wY6XoyC^)iGH
zZ~E9U^jWmX#mT(v^JJxJN0?BhLP<LIyM?qhuhvKo+mTGzP(s?^Gfx3S#Cd!$eSa6C
zvPy}Q7b>1_`>*GFDi3dv)GI{EOfK4f?mbHWRJ81A%(!Ijj%C8#r*ly}!XKYA3Bqcq
z!0fagI!CcFt9Q2`W5nlXiam&W{_IP+;;|>|Ue!s$N>$i(xcXfUD=jP?UAQE+ieWfY
zqf&k?LbTRkoHm(xDovsr&u+Ua-%vHnri+0S`S88Xz0(BBsCb4Zh;R&)3Zt6lG>3m0
z!-XmVDQq4u??OkWPeyM|&=>%HZ@lRjt%>DK@g>6v&^&kfXhg9W+8N)N?l?!YAra-R
zchk7koyYWJ_cM4JQ-GMJidye;fs$;*u&@<u_l@4d#5rFrq4wbbQy<a~zRT!hZx4*y
ztZ2_iS_)%sw=`91Lo3A<9Q~ZCvPH=?(J(i6y7eM#ci26$cEtQ{t2LDgo6frusD{p+
zP8XvPr%u$WYV%s*xcj*KG}?PDetb-MnY?|5%*`(c`i1YET+8s9AKo_{0>c}L=<@vM
z7QxOujoInZP3lXtOH?&3^}4$XmNZ!)aKDxNy;{gjnYGIjXwgtCR9N)bT-@wSGHBHE
z&fix2KRd{4Jkob1=}Ge&&E*&75bgtlo|T($dC(&k6ZGF4eJ3!|COsW7=}P9uk|)a`
z<M&<<m)aja*RFWUS;}zggSfbW;Aj$^o0x+rm8p?Ogh47zU5wu}?l2Mq9pU=tE%_<5
zgGICEw#g(%oTVm}snkmu9&Snt;AB>K$AuJP$k^8wp9pHFo8_cJWrC1TuUQBtSs{U~
z<1}^snUK`L`9s!p+Pj)WutvGksFz}rzL1|<NKN^xoE4PJ@jj@UwH%U<cMj6cj&zE`
zIm%pE-Z+FqRIOH^Os%5RFBfHm9ay-dKW0gDpm>r@c|C|JfGj!G5x~#l;4!6*|2*n%
zbPNgIZn($R>tCkbBE#}vuv&f!Q~cOwE3$`x(u*8i{0vzo=8+ZLF;SArT@J-8=}Gf2
zZrFgu({cmHh39d~szFKM@hVz8LRzKu7bK^aP}Vbzfhhj!&*OM6-=oOmItrIXJmQS`
zBCJmJgVwF6ZF$xw5+}0tWw|jWFe3&DDuzgg_YDCF6)_eB6uID$bzi8VP~V`{n|+@x
zBE9I$+b}UOAutIB1uG5%0vZJX1QhIiFj!FD7tg`@eugW&6*Uplxnl$fayG0o$E7Tx
H0s;sCd=XR|
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..16075e71f320303d91db3e74cafa01df05fbba6f
GIT binary patch
literal 36864
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
IX<`BVFWqWKA^-pY
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..ef4a50a9526fc10ac8140fe4a46e98d7cf727c01
GIT binary patch
literal 45056
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
YwJF|(xqDIH>m1rw`)b~lamYjd8+gHb$p8QV
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
new file mode 100644
index 0000000000..78691fd862
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-no-names.crt__server-no-names.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-no-names.crt__server-no-names.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-no-names.pfx b/src/test/ssl/ssl/nss/server-no-names.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..3db5c17f9c65ee8e38b435307569945f5230eef0
GIT binary patch
literal 3109
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-password.pfx b/src/test/ssl/ssl/nss/server-password.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..973ffd13c772f74dc2fcdbed87274f78fdbbbffe
GIT binary patch
literal 3197
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..f396d2216eddb9e8080c5f2a5398a0ad30a330d0
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..a3c30ee146f36500198bc5b7401260affff556e5
GIT binary patch
literal 45056
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
Daqo==
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
new file mode 100644
index 0000000000..c5e90c7000
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-revoked.crt__server-revoked.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-revoked.crt__server-revoked.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-revoked.pfx b/src/test/ssl/ssl/nss/server-revoked.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..52fd4217213867aa1dcdd8d0bbe428dbc54c7b7f
GIT binary patch
literal 3181
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
T0tf^MS6bnOG&(z40s;sCEb;V7
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..3105d1ccda3f613c65ac25c887fd054226aecbb7
GIT binary patch
literal 36864
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..ab1762634ccb5a0b9a25ed5606495032285f73e8
GIT binary patch
literal 45056
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
Zzq(1rW?-`iW_zVhN4x$u9TNuXe*hRnL686d
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
new file mode 100644
index 0000000000..82255d3f4e
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/ssl/nss/server-single-alt-name.pfx b/src/test/ssl/ssl/nss/server-single-alt-name.pfx
new file mode 100644
index 0000000000000000000000000000000000000000..3d0fee97935a0513dbaa0979b6754b885a6a7c71
GIT binary patch
literal 3213
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
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server.crl b/src/test/ssl/ssl/nss/server.crl
new file mode 100644
index 0000000000000000000000000000000000000000..769196dda6dfffeb4141ec007d6e276d3afb5e84
GIT binary patch
literal 418
zcmXqLVw`8t*lxhf#;Mij(e|B}k&&B~!NAGT!N87<Ih2K&$2=spxJ1F(Q6VkANFgA<
zxFo$OH8{{mAvoAap(qu|DK5^;&r>J?sVYt_DoZU=NKP#(DHi87v@kR@GB-3fF)%fa
z66ZBBGcYkUfpQH*41|~%+0YcBIfJ>0k&z)cx-<6WM&pI2r!*dm3egYCR+DZEcFbs=
zJLCSW&CW;U#5aBOwXHll$=p%k!bPiXhut}MT~u<8-g_X&?EdN?ab}a1JABr;+ig`=
z<67*aH7|L#+SOR4x3|MWJEoZ4vY)6nxx9MOgT+t(bDezlrzuZUzqqQ}z2kL-Q>O0U
zHbE<wjSIh&+P2+I(flFf&)N8NZ@bU8AI59GGNp869L-9#II(HANW;al9|>u9KQFgj
zG1J5K+BS|et`!}!yF)I0+pZ_l{^ZHDOBr?gx7Y$1Zp_zSS614#>}BGLu8NhBoGu3M
o9tXbUKYKSkd*T+M_r-Y*A{D<U7Q|28yuIn;%#d&L*G&^z0V1ELsQ>@~
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db b/src/test/ssl/ssl/nss/server_ca.crt.db/cert9.db
new file mode 100644
index 0000000000000000000000000000000000000000..0c8a303d1121dc443d26281cf1c9d688ff3613ea
GIT binary patch
literal 28672
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
CAa^7H
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db b/src/test/ssl/ssl/nss/server_ca.crt.db/key4.db
new file mode 100644
index 0000000000000000000000000000000000000000..7724801ab7b783dee9780314a10c797f4b0bc203
GIT binary patch
literal 36864
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
bOBt^0;uYb_MnN8~Y(fz+a(z_rCkp=p>@<Ih
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
new file mode 100644
index 0000000000..b81ced09e6
--- /dev/null
+++ b/src/test/ssl/ssl/nss/server_ca.crt.db/pkcs11.txt
@@ -0,0 +1,5 @@
+library=
+name=NSS Internal PKCS #11 Module
+parameters=configdir='sql:ssl/nss/server_ca.crt.db' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..00530d98af 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+}
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 89e1b39036..f141801478 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
On Thu, Sep 17, 2020 at 11:41:28AM +0200, Daniel Gustafsson wrote:
Attached is a v10 rebased to apply on top of HEAD.
I am afraid that this needs a new rebase. The patch is failing to
apply, per the CF bot. :/
--
Michael
On 29 Sep 2020, at 07:59, Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Sep 17, 2020 at 11:41:28AM +0200, Daniel Gustafsson wrote:
Attached is a v10 rebased to apply on top of HEAD.
I am afraid that this needs a new rebase. The patch is failing to
apply, per the CF bot. :/
It's failing on binary diffs due to the NSS certificate databases being
included to make hacking on the patch easier:
File src/test/ssl/ssl/nss/server.crl: git binary diffs are not supported.
This is a limitation of the CFBot patch tester, the text portions of the patch
still applies with a tiny but of fuzz.
cheers ./daniel
On 29 Sep 2020, at 09:52, Daniel Gustafsson <daniel@yesql.se> wrote:
On 29 Sep 2020, at 07:59, Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Sep 17, 2020 at 11:41:28AM +0200, Daniel Gustafsson wrote:
Attached is a v10 rebased to apply on top of HEAD.
I am afraid that this needs a new rebase. The patch is failing to
apply, per the CF bot. :/It's failing on binary diffs due to the NSS certificate databases being
included to make hacking on the patch easier:File src/test/ssl/ssl/nss/server.crl: git binary diffs are not supported.
This is a limitation of the CFBot patch tester, the text portions of the patch
still applies with a tiny but of fuzz.
Attached is a new version which doesn't contain the NSS certificate databases
to keep the CFBot happy.
It also implements server-side passphrase callbacks as well as re-enables the
tests for those. The callback works a bit differently from the OpenSSL one as
it must run in the forked process, so it can't run on server reload. There's
also no default fallback reading from a TTY like in OpenSSL, so if no callback
it set the always-failing dummy is set.
cheers ./daniel
Attachments:
0001-Support-for-NSS-as-a-TLS-backend-v11.patchapplication/octet-stream; name=0001-Support-for-NSS-as-a-TLS-backend-v11.patch; x-unix-mode=0644Download
From 60daa84e584988121c7a1157a6a8453a9d828057 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 2 Oct 2020 21:55:17 +0200
Subject: [PATCH] Support for NSS as a TLS backend v11
Daniel Gustafsson, Andrew Dunstan
---
configure | 211 +++
configure.ac | 30 +
contrib/Makefile | 2 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/acronyms.sgml | 43 +
doc/src/sgml/config.sgml | 28 +-
doc/src/sgml/installation.sgml | 30 +-
doc/src/sgml/libpq.sgml | 27 +-
doc/src/sgml/runtime.sgml | 78 +-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1170 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 984 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 176 +++
src/test/ssl/t/001_ssltests.pl | 326 +++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
40 files changed, 3575 insertions(+), 270 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index 19a3cd09a0..55ba9526df 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -857,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1559,6 +1561,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8106,6 +8109,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12180,6 +12218,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12442,6 +12483,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13344,6 +13536,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index 6b9d0487a8..970bebb24a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1235,6 +1247,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1410,6 +1435,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..d0b01cb2cc 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 10e23d02ed..e580a2369a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 06405f359c..a2196e3c17 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index b585f22408..60cd6ff8de 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -969,6 +969,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -985,6 +1010,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3315f1dd05..8b738bc412 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2474,6 +2474,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2497,9 +2499,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2526,6 +2533,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7958,6 +7969,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7984,6 +8000,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f975406acd..99f27b7ed9 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2377,7 +2400,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2424,6 +2447,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2551,6 +2582,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..8946fa696a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 36565df4fc..ea6d97585e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..b05906514a
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1170 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 596bcb7b84..ce6200c851 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..ef5f105afc 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index af27fee6b5..31a3377904 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dbc7e03697
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,984 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..4af971ef77 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +209,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +237,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +277,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +322,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..ff9c8ae3f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 89e1b39036..f141801478 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
The attached v12 adds support for pgcrypto as well as pg_strong_random, which I
believe completes the required subsystems where we have OpenSSL support today.
I opted for not adding code to handle the internal shaXXX implementations until
the dust settles around the proposal to change the API there.
Blowfish is not supported by NSS AFAICT, even though the cipher mechanism is
defined, so the internal implementation is used there instead. CAST5 is
supported, but segfaults inside NSS on most inputs so support for that is not
included for now.
cheers ./daniel
Attachments:
0001-Support-for-NSS-as-a-TLS-backend-v12.patchapplication/octet-stream; name=0001-Support-for-NSS-as-a-TLS-backend-v12.patch; x-unix-mode=0644Download
From 0cb0e6a0ce9adb18bc9d212bd03e4e09fa452972 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 8 Oct 2020 18:44:28 +0200
Subject: [PATCH] Support for NSS as a TLS backend v12
Daniel Gustafsson, Andrew Dunstan
---
configure | 223 +++-
configure.ac | 39 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 +++++++++++
contrib/pgcrypto/openssl.c | 2 +-
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/acronyms.sgml | 43 +
doc/src/sgml/config.sgml | 28 +-
doc/src/sgml/installation.sgml | 30 +-
doc/src/sgml/libpq.sgml | 27 +-
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/runtime.sgml | 78 +-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1158 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 6 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 984 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/port/pg_strong_random.c | 37 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 176 +++
src/test/ssl/t/001_ssltests.pl | 326 +++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
47 files changed, 4430 insertions(+), 284 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index 071d050ef0..e166c4cca6 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -857,6 +858,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1559,6 +1561,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8106,6 +8109,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12180,6 +12218,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12442,6 +12483,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13344,6 +13536,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18272,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
@@ -18107,6 +18320,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18116,7 +18335,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index e9ce611d23..255513a006 100644
--- a/configure.ac
+++ b/configure.ac
@@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1235,6 +1247,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1410,6 +1435,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2188,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2179,13 +2211,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..d0b01cb2cc 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..6c69ee5a6b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,16 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
+CF_SRCS = $(if $(subst no,,$(with_nss)), $(NSS_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_TESTS = $(if $(subst no,,$(with_nss)), $(NSS_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e0885cd221
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index ed96e4ce53..5ebe213406 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -400,7 +400,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
}
if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen))
- return PXE_ERR_GENERIC;
+ return PXE_ENCRYPT_FAILED;
return 0;
}
diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c
index 6a4681dae9..a243f575d3 100644
--- a/contrib/pgcrypto/px.c
+++ b/contrib/pgcrypto/px.c
@@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = {
{PXE_MCRYPT_INTERNAL, "mcrypt internal error"},
{PXE_NO_RANDOM, "Failed to generate strong random bits"},
{PXE_DECRYPT_FAILED, "Decryption failed"},
+ {PXE_ENCRYPT_FAILED, "Encryption failed"},
{PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"},
{PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"},
{PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"},
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 5487923edb..17d6f22498 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -61,6 +61,7 @@
#define PXE_MCRYPT_INTERNAL -16
#define PXE_NO_RANDOM -17
#define PXE_DECRYPT_FAILED -18
+#define PXE_ENCRYPT_FAILED -19
#define PXE_PGP_CORRUPT_DATA -100
#define PXE_PGP_CORRUPT_ARMOR -101
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 3ac588dfb5..c867b87900 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -969,6 +969,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -985,6 +1010,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index de60281fcb..c127309d76 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..8946fa696a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 36565df4fc..ea6d97585e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..a3072717e1
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1158 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * 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 cipher. If there are no user preferred suites,
+ * set the domestic policy. 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, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,12 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..ef5f105afc 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b0ca37c2ed..a15a89c50c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dbc7e03697
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,984 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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 "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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. 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.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ 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->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), ca_trust_name, err);
+ 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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ 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.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), server_cert,
+ checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..bac541078a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..4af971ef77 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +209,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +237,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +277,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +322,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..ff9c8ae3f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..d6aefbfcbf 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
Hi,
On 2020-10-20 14:24:24 +0200, Daniel Gustafsson wrote:
From 0cb0e6a0ce9adb18bc9d212bd03e4e09fa452972 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 8 Oct 2020 18:44:28 +0200
Subject: [PATCH] Support for NSS as a TLS backend v12
---
configure | 223 +++-
configure.ac | 39 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 +++++++++++
contrib/pgcrypto/openssl.c | 2 +-
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +
Personally I'd like to see this patch broken up a bit - it's quite
large. Several of the changes could easily be committed separately, no?
if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fi
Based on a quick look there's no similar error check for the msvc
build. Should there be?
+if test "$with_nss" = yes ; then + if test x"$with_openssl" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fi
Isn't this a repetition of the earlier check?
+ CLEANLDFLAGS="$LDFLAGS" + # TODO: document this set of LDFLAGS + LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
Shouldn't this use nss-config or such?
+if test "$with_nss" = yes ; then + AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])]) + AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])]) +fi
Hm. For me, on debian, these headers are not directly in the default
include search path, but would be as nss/ssl.h. I don't see you adding
nss/ to CFLAGS anywhere? How does this work currently?
I think it'd also be better if we could include these files as nss/ssl.h
etc - ssl.h is a name way too likely to conflict imo.
+++ b/src/backend/libpq/be-secure-nss.c @@ -0,0 +1,1158 @@ +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE
Most compilers/preprocessors don't warn about redefinitions when they
would result in the same value (IIRC we have some cases of redefinitions
in tree even). Does nspr's differ?
+/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * The definitions are however not actually used in NSPR at all, and are only + * intended for what seems to be backwards compatibility for apps written + * against old versions of NSPR. The following comment is in the referenced + * file, and was added in 1998: + * + * This section typedefs the old 'native' types to the new PR<type>s. + * These definitions are scheduled to be eliminated at the earliest + * possible time. The NSPR API is implemented and documented using + * the new definitions. + * + * As there is no opt-out from pulling in these typedefs, we define the guard + * for the file to exclude it. This is incredibly ugly, but seems to be about + * the only way around it. + */ +#define PROTYPES_H +#include <nspr.h> +#undef PROTYPES_H
Yuck :(.
+int +be_tls_init(bool isServerStart) +{ + SECStatus status; + SSLVersionRange supported_sslver; + + /* + * Set up the connection cache for multi-processing application behavior.
Hm. Do we necessarily want that? Session resumption is not exactly
unproblematic... Or does this do something else?
+ * If we are in ServerStart then we initialize the cache. If the server is + * already started, we inherit the cache such that it can be used for + * connections. Calling SSL_ConfigMPServerSIDCache sets an environment + * variable which contains enough information for the forked child to know + * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will + * make the forked child look it up by the default name SSL_INHERITANCE, + * if env vars aren't inherited then the contents of the variable can be + * passed instead. + */
Does this stuff work on windows / EXEC_BACKEND?
+ * 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. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_Init
says that currently all parameters are ignored?
+ /* + * 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. + */ + pr_fd = PR_ImportTCPSocket(port->sock); + if (!pr_fd) + ereport(ERROR, + (errmsg("unable to connect to socket")));
I don't see the comment you're referring to?
+ /* + * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family); + if (!model) + ereport(ERROR, + (errmsg("unable to open socket"))); + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + model = SSL_ImportFD(NULL, model); + if (!model) + ereport(ERROR, + (errmsg("unable to enable TLS on socket")));
It's confusing that these functions do not actually reference the socket
via some handle :(. What does opening a socket do here?
+ /* + * Configure the allowed cipher. If there are no user preferred suites,
*ciphers?
+ + port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + ereport(ERROR, + (errmsg("unable to initialize"))); + + PR_Close(model);
A comment explaining why we first import a NULL into the model, and then
release the model, and import the real fd would be good.
+ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_read; + PRErrorCode err; + + n_read = PR_Read(port->pr_fd, ptr, len); + + if (n_read < 0) + { + err = PR_GetError();
Yay, more thread global state :(.
+ /* XXX: This logic seems potentially bogus? */ + if (err == PR_WOULD_BLOCK_ERROR) + *waitfor = WL_SOCKET_READABLE; + else + *waitfor = WL_SOCKET_WRITEABLE;
Don't we need to handle failed connections somewhere here? secure_read()
won't know about PR_GetError() etc? How would SSL errors be signalled
upwards here?
Also, as you XXX, it's not clear to me that your mapping would always
result in waiting for the right event? A tls write could e.g. very well
require receiving data etc?
+ /* + * At least one byte with password content was returned, and NSS requires + * that we return it allocated in NSS controlled memory. If we fail to + * allocate then abort without passing back NULL and bubble up the error + * on the PG side. + */ + password = (char *) PR_Malloc(len + 1); + if (!password) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory")));+ strlcpy(password, buf, sizeof(password)); + explicit_bzero(buf, sizeof(buf)); +
In case of error you're not bzero'ing out the password!
Separately, I wonder if we should introduce a function for throwing OOM
errors - which then e.g. could print the memory context stats in those
places too...
+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;
Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.
Also, what's up with the CN= bit? Why is that needed here, but not for
openssl?
+static PRFileDesc * +init_iolayer(Port *port, int loglevel) +{ + const PRIOMethods *default_methods; + PRFileDesc *layer; + + /* + * Start by initializing our layer with all the default methods so that we + * can selectively override the ones we want while still ensuring that we + * have a complete layer specification. + */ + default_methods = PR_GetDefaultIOMethods(); + memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods)); + + pr_iomethods.recv = pg_ssl_read; + pr_iomethods.send = pg_ssl_write; + + /* + * Each IO layer must be identified by a unique name, where uniqueness is + * per connection. Each connection in a postgres cluster can generate the + * identity from the same string as they will create their IO layers on + * different sockets. Only one layer per socket can have the same name. + */ + pr_id = PR_GetUniqueIdentity("PostgreSQL");
Seems like it might not be a bad idea to append Server or something?
+ + /* + * Create the actual IO layer as a stub such that it can be pushed onto + * the layer stack. The step via a stub is required as we define custom + * callbacks. + */ + layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods); + if (!layer) + { + ereport(loglevel, + (errmsg("unable to create NSS I/O layer"))); + return NULL; + }
Why is this accepting a variable log level? The only caller passes ERROR?
+/* + * pg_SSLerrmessage + * Create and return a human readable error message given + * the specified error code + * + * PR_ErrorToName only converts the enum identifier of the error to string, + * but that can be quite useful for debugging (and in case PR_ErrorToString is + * unable to render a message then we at least have something). + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + char error[128]; + int ret; + + /* TODO: this should perhaps use a StringInfo instead.. */ + ret = pg_snprintf(error, sizeof(error), "%s (%s)", + PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT), + PR_ErrorToName(errcode)); + if (ret) + return pstrdup(error);
+ return pstrdup(_("unknown TLS error")); +}
Why not use psrintf() here?
+++ b/src/include/common/pg_nss.h @@ -0,0 +1,141 @@ +/*------------------------------------------------------------------------- + * + * pg_nss.h + * Support for NSS as a TLS backend + * + * These definitions are used by both frontend and backend code. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/common/pg_nss.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_NSS_H +#define PG_NSS_H + +#ifdef USE_NSS + +#include <sslproto.h> + +PRUint16 pg_find_cipher(char *name); + +typedef struct +{ + const char *name; + PRUint16 number; +} NSSCiphers; + +#define INVALID_CIPHER 0xFFFF + +/* + * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h + * in order to provide a human readable version of the ciphers. It would be + * nice to not have to have this, but NSS doesn't provide any API addressing + * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less? + */ +static const NSSCiphers NSS_CipherList[] = { + + {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
Hm. Is this whole business of defining array constants in a header just
done to avoid having a .c file that needs to be compiled both in
frontend and backend code?
+/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * The definitions are however not actually used in NSPR at all, and are only + * intended for what seems to be backwards compatibility for apps written + * against old versions of NSPR. The following comment is in the referenced + * file, and was added in 1998: + * + * This section typedefs the old 'native' types to the new PR<type>s. + * These definitions are scheduled to be eliminated at the earliest + * possible time. The NSPR API is implemented and documented using + * the new definitions. + * + * As there is no opt-out from pulling in these typedefs, we define the guard + * for the file to exclude it. This is incredibly ugly, but seems to be about + * the only way around it. + */
There's a lot of duplicated comments here. Could we move either of the
files to reference the other for longer ones?
+/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in <private/pprio.h>, but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
Ugh. This is really the way to do this? How do other applications deal
with this problem?
+#if defined(WIN32) +static const char *ca_trust_name = "nssckbi.dll"; +#elif defined(__darwin__) +static const char *ca_trust_name = "libnssckbi.dylib"; +#else +static const char *ca_trust_name = "libnssckbi.so"; +#endif
There's really no pre-existing handling for this in nss???
+ /* + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456 + * + * The InitParameters struct passed can be used to override internal + * values in NSS, but the usage is not documented at all. When using + * NSS_Init initializations, the values are instead set via PK11_Configure + * calls so the PK11_Configure documentation can be used to glean some + * details on these. + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ + if (!nss_context) + { + char *err = pg_SSLerrmessage(PR_GetError()); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to %s certificate database: %s"), + conn->cert_database ? "open" : "create", + err); + free(err); + return PGRES_POLLING_FAILED; + } + + /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy();
Why is "domestic" the right thing here?
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
Is it actually OK to do stuff like this when other users of NSS might be
present? That's obviously more likely in the libpq case, compared to the
backend case (where it's also possible, of course). What prevents us
from overriding another user's callback?
+ssize_t +pgtls_read(PGconn *conn, void *ptr, size_t len) +{ + PRInt32 nread; + PRErrorCode status; + int read_errno = 0; + + nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT); + + /* + * PR_Recv blocks until there is data to read or the timeout expires. Zero + * is returned for closed connections, while -1 indicates an error within + * the ongoing connection. + */ + if (nread == 0) + { + read_errno = ECONNRESET; + return -1; + }
It's a bit confusing to talk about blocking when the socket presumably
is in non-blocking mode, and you're also asking to never wait?
+ if (nread == -1) + { + status = PR_GetError(); + + switch (status) + { + case PR_WOULD_BLOCK_ERROR: + read_errno = EINTR; + break;
Uh, isn't this going to cause a busy-loop by the caller? EINTR isn't the
same as EAGAIN/EWOULDBLOCK?
+ case PR_IO_TIMEOUT_ERROR:
+ break;
What does this mean? We'll return with a 0 errno here, right? When is
this case reachable?
E.g. the comment in fe-misc.c:
/* pqsecure_read set the error message for us */
for this case doesn't seem to be fulfilled by this.
+/* + * Verify that the server certificate matches the hostname we connected to. + * + * The certificate's Common Name and Subject Alternative Names are considered. + */ +int +pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, + int *names_examined, + char **first_name) +{ + return 1; +}
Uh, huh? Certainly doesn't verify anything...
+/* ------------------------------------------------------------ */ +/* PostgreSQL specific TLS support functions */ +/* ------------------------------------------------------------ */ + +/* + * TODO: this a 99% copy of the same function in the backend, make these share + * a single implementation instead. + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + const char *error; + + error = PR_ErrorToName(errcode); + if (error) + return strdup(error); + + return strdup("unknown TLS error"); +}
Btw, why does this need to duplicate strings, instead of returning a
const char*?
Greetings,
Andres Freund
On 20 Oct 2020, at 21:15, Andres Freund <andres@anarazel.de> wrote:
Hi,
Thanks for your review, much appreciated!
On 2020-10-20 14:24:24 +0200, Daniel Gustafsson wrote:
From 0cb0e6a0ce9adb18bc9d212bd03e4e09fa452972 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 8 Oct 2020 18:44:28 +0200
Subject: [PATCH] Support for NSS as a TLS backend v12
---
configure | 223 +++-
configure.ac | 39 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 +++++++++++
contrib/pgcrypto/openssl.c | 2 +-
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +Personally I'd like to see this patch broken up a bit - it's quite
large. Several of the changes could easily be committed separately, no?
Not sure how much of this makes sense committed separately (unless separately
means in quick succession), but it could certainly be broken up for the sake of
making review easier. I will take a stab at that, but in a follow-up email as
I would like the split to be a version just doing the split and not also
introducing/fixing things.
if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fiBased on a quick look there's no similar error check for the msvc
build. Should there be?
Thats a good question. When embarking on this is seemed quite natural to me
that it should be, but now I'm not so sure. Maybe there should be a
--with-openssl-preferred like how we handle readline/libedit or just allow
multiple and let the last one win? Do you have any input on what would make
sense?
The only thing I think makes no sense is to allow multiple ones at the same
time given the current autoconf switches, even if it would just be to pick say
pg_strong_random from one and libpq TLS from another.
+if test "$with_nss" = yes ; then + if test x"$with_openssl" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fiIsn't this a repetition of the earlier check?
It is, and it we want to keep such a check it should be broken out into a
separate step performed before all library specific checks IMO.
+ CLEANLDFLAGS="$LDFLAGS" + # TODO: document this set of LDFLAGS + LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"Shouldn't this use nss-config or such?
Indeed it should, where available. I've added rudimentary support for that
without a fallback as of now.
+if test "$with_nss" = yes ; then + AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for NSS])]) + AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required for NSS])]) +fiHm. For me, on debian, these headers are not directly in the default
include search path, but would be as nss/ssl.h. I don't see you adding
nss/ to CFLAGS anywhere? How does this work currently?
I had Stockholm-syndromed myself into passing --with-includes and hadn't really
realized. Sometimes the obvious is too obvious in a 4000+ LOC patch.
I think it'd also be better if we could include these files as nss/ssl.h
etc - ssl.h is a name way too likely to conflict imo.
I've changed this to be nss/ssl.h and nspr/nspr.h etc, but the include path
will still need the direct path to the headers (from autoconf) since nss.h
includes NSPR headers as #include <nspr.h> and so on.
+++ b/src/backend/libpq/be-secure-nss.c @@ -0,0 +1,1158 @@ +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTEMost compilers/preprocessors don't warn about redefinitions when they
would result in the same value (IIRC we have some cases of redefinitions
in tree even). Does nspr's differ?
GCC 8.3 in my Debian installation throws the below warning:
In file included from /usr/include/nspr/prtypes.h:26,
from /usr/include/nspr/pratom.h:14,
from /usr/include/nspr/nspr.h:9,
from be-secure-nss.c:45:
/usr/include/nspr/prcpucfg.h:1143: warning: "BITS_PER_BYTE" redefined
#define BITS_PER_BYTE PR_BITS_PER_BYTE
In file included from ../../../src/include/c.h:55,
from ../../../src/include/postgres.h:46,
from be-secure-nss.c:16:
../../../src/include/pg_config_manual.h:115: note: this is the location of the previous definition
#define BITS_PER_BYTE 8
PR_BITS_PER_BYTE is defined per platform in pr/include/md/_<platform>.cfg and
is as expected 8. I assume it's that indirection which cause the warning?
+/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * The definitions are however not actually used in NSPR at all, and are only + * intended for what seems to be backwards compatibility for apps written + * against old versions of NSPR. The following comment is in the referenced + * file, and was added in 1998: + * + * This section typedefs the old 'native' types to the new PR<type>s. + * These definitions are scheduled to be eliminated at the earliest + * possible time. The NSPR API is implemented and documented using + * the new definitions. + * + * As there is no opt-out from pulling in these typedefs, we define the guard + * for the file to exclude it. This is incredibly ugly, but seems to be about + * the only way around it. + */ +#define PROTYPES_H +#include <nspr.h> +#undef PROTYPES_HYuck :(.
Thats not an understatement. Taking another dive into the NSPR code I did
however find a proper way to deal with this. Defining NO_NSPR_10_SUPPORT stops
NSPR from using the files in obsolete/. So fixed, yay!
+int +be_tls_init(bool isServerStart) +{ + SECStatus status; + SSLVersionRange supported_sslver; + + /* + * Set up the connection cache for multi-processing application behavior.Hm. Do we necessarily want that? Session resumption is not exactly
unproblematic... Or does this do something else?
From my reading of the docs, and experience with the code, a server application
must set up a connection cache in order to accept connections. Not entirely
sure, and the docs aren't terribly clear for non SSLv2/v3 environments (it
seems to only cache for SSLv2/3 and not TLSv+) but it seems like it may have
other uses internally. I will hunt down some more information on the NSS
mailing list.
+ * If we are in ServerStart then we initialize the cache. If the server is + * already started, we inherit the cache such that it can be used for + * connections. Calling SSL_ConfigMPServerSIDCache sets an environment + * variable which contains enough information for the forked child to know + * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will + * make the forked child look it up by the default name SSL_INHERITANCE, + * if env vars aren't inherited then the contents of the variable can be + * passed instead. + */Does this stuff work on windows
According to the documentation it does, and Andrew had this working on Windows
in an earlier version of the patch. I need to get a proper Windows env for
testing/dev up and running as mine has bitrotted to nothingness.
/ EXEC_BACKEND?
That's a good point, maybe we need to do a SSL_ConfigServerSessionIDCache
rather than the MP version for EXEC_BACKEND? Not sure.
+ * 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. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_Init
says that currently all parameters are ignored?
Right, my comment didn't reflect that they're all dead these days, only that
one of them has been unused since RUN DMC topped the charts with "It's like
that". Comment updated.
+ /* + * 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. + */ + pr_fd = PR_ImportTCPSocket(port->sock); + if (!pr_fd) + ereport(ERROR, + (errmsg("unable to connect to socket")));I don't see the comment you're referring to?
It's referring to the comment discussing PR_ImportTCPSocket being a private API
call, yet still used by everyone (which is also discussed later in this review).
+ /* + * 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 = PR_OpenTCPSocket(port->laddr.addr.ss_family); + if (!model) + ereport(ERROR, + (errmsg("unable to open socket"))); + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + model = SSL_ImportFD(NULL, model); + if (!model) + ereport(ERROR, + (errmsg("unable to enable TLS on socket")));It's confusing that these functions do not actually reference the socket
via some handle :(. What does opening a socket do here?
This specific call converts the socket from a plain NSPR socket to an SSL/TLS
capable socket which NSS will work with. This is a required step for
"activating" NSS on the socket.
+ /* + * Configure the allowed cipher. If there are no user preferred suites,*ciphers?
Yes, fixed.
+ + port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + ereport(ERROR, + (errmsg("unable to initialize"))); + + PR_Close(model);A comment explaining why we first import a NULL into the model, and then
release the model, and import the real fd would be good.
I've added a small comment to explain how the model is a configuration template
for the actual socket. This part of NSS/NSPR is a bit overcomplicated for how
we have connections, it's more geared towards having many open sockets in the
same process.
+ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_read; + PRErrorCode err; + + n_read = PR_Read(port->pr_fd, ptr, len); + + if (n_read < 0) + { + err = PR_GetError();Yay, more thread global state :(.
Sorry about that.
+ /* XXX: This logic seems potentially bogus? */ + if (err == PR_WOULD_BLOCK_ERROR) + *waitfor = WL_SOCKET_READABLE; + else + *waitfor = WL_SOCKET_WRITEABLE;Don't we need to handle failed connections somewhere here? secure_read()
won't know about PR_GetError() etc? How would SSL errors be signalled
upwards here?Also, as you XXX, it's not clear to me that your mapping would always
result in waiting for the right event? A tls write could e.g. very well
require receiving data etc?
Fixed, but there might be more to be done here.
+ /* + * At least one byte with password content was returned, and NSS requires + * that we return it allocated in NSS controlled memory. If we fail to + * allocate then abort without passing back NULL and bubble up the error + * on the PG side. + */ + password = (char *) PR_Malloc(len + 1); + if (!password) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory")));+ strlcpy(password, buf, sizeof(password)); + explicit_bzero(buf, sizeof(buf)); +In case of error you're not bzero'ing out the password!
Fixed.
Separately, I wonder if we should introduce a function for throwing OOM
errors - which then e.g. could print the memory context stats in those
places too...
+1. I'd be happy to review such a patch.
+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.
We should, but it's proving rather difficult as there is no equivalent API call
to get the string as well as the expected length of it.
Also, what's up with the CN= bit? Why is that needed here, but not for
openssl?
OpenSSL returns only the value portion, whereas NSS returns key=value so we
need to skip over the key= part.
+static PRFileDesc * +init_iolayer(Port *port, int loglevel) +{ + const PRIOMethods *default_methods; + PRFileDesc *layer; + + /* + * Start by initializing our layer with all the default methods so that we + * can selectively override the ones we want while still ensuring that we + * have a complete layer specification. + */ + default_methods = PR_GetDefaultIOMethods(); + memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods)); + + pr_iomethods.recv = pg_ssl_read; + pr_iomethods.send = pg_ssl_write; + + /* + * Each IO layer must be identified by a unique name, where uniqueness is + * per connection. Each connection in a postgres cluster can generate the + * identity from the same string as they will create their IO layers on + * different sockets. Only one layer per socket can have the same name. + */ + pr_id = PR_GetUniqueIdentity("PostgreSQL");Seems like it might not be a bad idea to append Server or something?
Fixed.
+ /* + * Create the actual IO layer as a stub such that it can be pushed onto + * the layer stack. The step via a stub is required as we define custom + * callbacks. + */ + layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods); + if (!layer) + { + ereport(loglevel, + (errmsg("unable to create NSS I/O layer"))); + return NULL; + }Why is this accepting a variable log level? The only caller passes ERROR?
Good catch, that's a leftover from a previous version which no longer makes
sense. loglevel param removed.
+/* + * pg_SSLerrmessage + * Create and return a human readable error message given + * the specified error code + * + * PR_ErrorToName only converts the enum identifier of the error to string, + * but that can be quite useful for debugging (and in case PR_ErrorToString is + * unable to render a message then we at least have something). + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + char error[128]; + int ret; + + /* TODO: this should perhaps use a StringInfo instead.. */ + ret = pg_snprintf(error, sizeof(error), "%s (%s)", + PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT), + PR_ErrorToName(errcode)); + if (ret) + return pstrdup(error);+ return pstrdup(_("unknown TLS error")); +}Why not use psrintf() here?
Thats a good question to which I don't have a good answer. Changed to doing
just that.
+++ b/src/include/common/pg_nss.h @@ -0,0 +1,141 @@ +/*------------------------------------------------------------------------- + * + * pg_nss.h + * Support for NSS as a TLS backend + * + * These definitions are used by both frontend and backend code. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/common/pg_nss.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_NSS_H +#define PG_NSS_H + +#ifdef USE_NSS + +#include <sslproto.h> + +PRUint16 pg_find_cipher(char *name); + +typedef struct +{ + const char *name; + PRUint16 number; +} NSSCiphers; + +#define INVALID_CIPHER 0xFFFF + +/* + * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h + * in order to provide a human readable version of the ciphers. It would be + * nice to not have to have this, but NSS doesn't provide any API addressing + * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less? + */ +static const NSSCiphers NSS_CipherList[] = { + + {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},Hm. Is this whole business of defining array constants in a header just
done to avoid having a .c file that needs to be compiled both in
frontend and backend code?
That was the original motivation, but I guess I should just bit the bullet and
make it a .c compiled in both frontend and backend?
+/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * The definitions are however not actually used in NSPR at all, and are only + * intended for what seems to be backwards compatibility for apps written + * against old versions of NSPR. The following comment is in the referenced + * file, and was added in 1998: + * + * This section typedefs the old 'native' types to the new PR<type>s. + * These definitions are scheduled to be eliminated at the earliest + * possible time. The NSPR API is implemented and documented using + * the new definitions. + * + * As there is no opt-out from pulling in these typedefs, we define the guard + * for the file to exclude it. This is incredibly ugly, but seems to be about + * the only way around it. + */There's a lot of duplicated comments here. Could we move either of the
files to reference the other for longer ones?
I took a stab at this in the attached version. The code is perhaps over-
commented in parts but I tried to encode my understanding of NSS into the
comments where documentation is lacking, since I assume I'm not the only one
who is new to NSS. There might be a need to pare back to keep it focused in
case this patch goes futher.
+/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in <private/pprio.h>, but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);Ugh. This is really the way to do this? How do other applications deal
with this problem?
They either #include <private/pprio.h> or they do it like this (or vendor NSPR
which makes calling private APIs less problematic). It sure is ugly, but there
is no alternative to using this function.
+#if defined(WIN32) +static const char *ca_trust_name = "nssckbi.dll"; +#elif defined(__darwin__) +static const char *ca_trust_name = "libnssckbi.dylib"; +#else +static const char *ca_trust_name = "libnssckbi.so"; +#endifThere's really no pre-existing handling for this in nss???
NSS_Init does have more or less the above logic (see snippet below), but only
when there is a cert database defined.
/*
* The following code is an attempt to automagically find the external root
* module.
* Note: Keep the #if-defined chunks in order. HPUX must select before UNIX.
*/
static const char *dllname =
#if defined(XP_WIN32) || defined(XP_OS2)
"nssckbi.dll";
#elif defined(HPUX) && !defined(__ia64) /* HP-UX PA-RISC */
"libnssckbi.sl";
#elif defined(DARWIN)
"libnssckbi.dylib";
#elif defined(XP_UNIX) || defined(XP_BEOS)
"libnssckbi.so";
#else
#error "Uh! Oh! I don't know about this platform."
#endif
In the NSS_INIT_NOCERTDB case there is no such handling of the libname provided
by NSS so we need to do that ourselves.
+ /* + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456 + * + * The InitParameters struct passed can be used to override internal + * values in NSS, but the usage is not documented at all. When using + * NSS_Init initializations, the values are instead set via PK11_Configure + * calls so the PK11_Configure documentation can be used to glean some + * details on these. + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs+ + if (!nss_context) + { + char *err = pg_SSLerrmessage(PR_GetError()); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to %s certificate database: %s"), + conn->cert_database ? "open" : "create", + err); + free(err); + return PGRES_POLLING_FAILED; + } + + /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy();Why is "domestic" the right thing here?
Historically there are three cipher policies in NSS: Domestic, Export and
France. These would enable a set of ciphers based on US export restrictions
(domest/export) or French import restrictions. All ciphers would start
disabled and then the ciphers belonging to the chosen set would be enabled.
Long ago, that was however removed and they now all get enabled by calling
either of these three functions. NSS_SetDomesticPolicy enables all implemented
ciphers, and the other calls just call NSS_SetDomesticPolicy, I guess that API
was kept for backwards compatibility. The below bugzilla entry has a bit more
information on this:
https://bugzilla.mozilla.org/show_bug.cgi?id=848384
That being said, the comment in the code did not reflect that, so I've reworded
it hoping it will be clearer now.
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);Is it actually OK to do stuff like this when other users of NSS might be
present? That's obviously more likely in the libpq case, compared to the
backend case (where it's also possible, of course). What prevents us
from overriding another user's callback?
The password callback pointer is stored in a static variable in NSS (in the
file lib/pk11wrap/pk11auth.c).
+ssize_t +pgtls_read(PGconn *conn, void *ptr, size_t len) +{ + PRInt32 nread; + PRErrorCode status; + int read_errno = 0; + + nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT); + + /* + * PR_Recv blocks until there is data to read or the timeout expires. Zero + * is returned for closed connections, while -1 indicates an error within + * the ongoing connection. + */ + if (nread == 0) + { + read_errno = ECONNRESET; + return -1; + }It's a bit confusing to talk about blocking when the socket presumably
is in non-blocking mode, and you're also asking to never wait?
Fair enough, I can agree that the wording isn't spot on. The socket is
non-blocking while PR_Recv can block (which is what we ask it not to). I've
reworded and moved the comment around to hopefully make it clearer.
+ if (nread == -1) + { + status = PR_GetError(); + + switch (status) + { + case PR_WOULD_BLOCK_ERROR: + read_errno = EINTR; + break;Uh, isn't this going to cause a busy-loop by the caller? EINTR isn't the
same as EAGAIN/EWOULDBLOCK?
Right, that's clearly not right.
+ case PR_IO_TIMEOUT_ERROR:
+ break;What does this mean? We'll return with a 0 errno here, right? When is
this case reachable?
It should, AFAICT, only be reachable when PR_Recv is used with a timeout which
we don't do. It mentioned somewhere that it had happened in no-wait calls due
to a bug, but I fail to find that reference now. Either way, I've removed it
to fall into the default error handling which now sets errno correctly as that
was a paddle short here.
E.g. the comment in fe-misc.c:
/* pqsecure_read set the error message for us */
for this case doesn't seem to be fulfilled by this.
Fixed, I hope.
+/* + * Verify that the server certificate matches the hostname we connected to. + * + * The certificate's Common Name and Subject Alternative Names are considered. + */ +int +pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, + int *names_examined, + char **first_name) +{ + return 1; +}Uh, huh? Certainly doesn't verify anything...
Doh, the verification was done as part of the cert validation callback and I
had missed moving it to the stub. Fixed and also expanded to closer match how
it's done in the OpenSSL implementation.
+/* ------------------------------------------------------------ */ +/* PostgreSQL specific TLS support functions */ +/* ------------------------------------------------------------ */ + +/* + * TODO: this a 99% copy of the same function in the backend, make these share + * a single implementation instead. + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + const char *error; + + error = PR_ErrorToName(errcode); + if (error) + return strdup(error); + + return strdup("unknown TLS error"); +}Btw, why does this need to duplicate strings, instead of returning a
const char*?
No, it doesn't, and no longer does.
The attached includes fixes for the above mentioned issues (and a few small
other ones I stumbled across), hopefully without introducing too many new. As
mentioned, I'll perform the split into multiple patches in a separate version
which only performs a split to make it easier to diff the individual patchfile
versions.
cheers ./daniel
Attachments:
0001-Support-for-NSS-as-a-TLS-backend-v13.patchapplication/octet-stream; name=0001-Support-for-NSS-as-a-TLS-backend-v13.patch; x-unix-mode=0644Download
From ed0da4793cb211cced0cd8812c648fb63e066bb6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 27 Oct 2020 21:03:41 +0100
Subject: [PATCH] Support for NSS as a TLS backend v13
Daniel Gustafsson, Andrew Dunstan
---
configure | 342 ++++-
configure.ac | 50 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 +++++++++++
contrib/pgcrypto/openssl.c | 2 +-
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++-
doc/src/sgml/acronyms.sgml | 43 +
doc/src/sgml/config.sgml | 28 +-
doc/src/sgml/installation.sgml | 30 +-
doc/src/sgml/libpq.sgml | 27 +-
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/runtime.sgml | 78 +-
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1175 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 6 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 11 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1018 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/port/pg_strong_random.c | 37 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 176 +++
src/test/ssl/t/001_ssltests.pl | 326 +++--
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 +
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
48 files changed, 4618 insertions(+), 286 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/configure b/configure
index ace4ed5dec..412f40b58a 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +713,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -857,6 +860,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1559,6 +1563,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8106,6 +8111,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12180,6 +12220,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12442,6 +12485,274 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13344,6 +13655,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18391,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
@@ -18107,6 +18439,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18116,7 +18454,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..86311946d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1235,6 +1247,30 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1410,6 +1446,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2199,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2179,13 +2222,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..d0b01cb2cc 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..6c69ee5a6b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,16 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
+CF_SRCS = $(if $(subst no,,$(with_nss)), $(NSS_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_TESTS = $(if $(subst no,,$(with_nss)), $(NSS_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index ed96e4ce53..5ebe213406 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -400,7 +400,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
}
if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen))
- return PXE_ERR_GENERIC;
+ return PXE_ENCRYPT_FAILED;
return 0;
}
diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c
index 6a4681dae9..a243f575d3 100644
--- a/contrib/pgcrypto/px.c
+++ b/contrib/pgcrypto/px.c
@@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = {
{PXE_MCRYPT_INTERNAL, "mcrypt internal error"},
{PXE_NO_RANDOM, "Failed to generate strong random bits"},
{PXE_DECRYPT_FAILED, "Decryption failed"},
+ {PXE_ENCRYPT_FAILED, "Encryption failed"},
{PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"},
{PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"},
{PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"},
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 5487923edb..17d6f22498 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -61,6 +61,7 @@
#define PXE_MCRYPT_INTERNAL -16
#define PXE_NO_RANDOM -17
#define PXE_DECRYPT_FAILED -18
+#define PXE_ENCRYPT_FAILED -19
#define PXE_PGP_CORRUPT_DATA -100
#define PXE_PGP_CORRUPT_ARMOR -101
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..8946fa696a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2b184be3d1
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1175 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4c86fb6087..8821c60a34 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..9205f0fc3b
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,12 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..7b396d04af 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,9 +45,13 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
+ifeq ($(with_ssl),yes)
+OBJS += \
+ fe-secure-common.o
+endif
+
ifeq ($(with_openssl),yes)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
@@ -57,6 +61,11 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b0ca37c2ed..a15a89c50c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..395147f551
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1018 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ default:
+ read_errno = ECONNRESET;
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..33af92a60b 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
diff --git a/src/test/Makefile b/src/test/Makefile
index 9774f534d9..a09c350939 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..4af971ef77 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +209,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +237,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +277,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +322,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..ff9c8ae3f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..d6aefbfcbf 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
On 27/10/2020 22:07, Daniel Gustafsson wrote:
/*
* Track whether the NSS database has a password set or not. There is no API
* function for retrieving password status, so 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.
*/
static bool has_password = false;
This is set in PQssl_passwd_cb function, but never reset. That seems
wrong. The NSS database used in one connection might have a password,
while another one might not. Or have I completely misunderstood this?
- Heikki
Hi,
On 2020-10-27 21:07:01 +0100, Daniel Gustafsson wrote:
On 2020-10-20 14:24:24 +0200, Daniel Gustafsson wrote:
From 0cb0e6a0ce9adb18bc9d212bd03e4e09fa452972 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 8 Oct 2020 18:44:28 +0200
Subject: [PATCH] Support for NSS as a TLS backend v12
---
configure | 223 +++-
configure.ac | 39 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 +++++++++++
contrib/pgcrypto/openssl.c | 2 +-
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +Personally I'd like to see this patch broken up a bit - it's quite
large. Several of the changes could easily be committed separately, no?Not sure how much of this makes sense committed separately (unless separately
means in quick succession), but it could certainly be broken up for the sake of
making review easier.
Committing e.g. the pgcrypto pieces separately from the backend code
seems unproblematic. But yes, I would expect them to go in close to each
other. I'm mainly concerned with smaller review-able units.
Have you done testing to ensure that NSS PG cooperates correctly with
openssl PG? Is there a way we can make that easier to do? E.g. allowing
to build frontend with NSS and backend with openssl and vice versa?
if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fiBased on a quick look there's no similar error check for the msvc
build. Should there be?Thats a good question. When embarking on this is seemed quite natural to me
that it should be, but now I'm not so sure. Maybe there should be a
--with-openssl-preferred like how we handle readline/libedit or just allow
multiple and let the last one win? Do you have any input on what would make
sense?The only thing I think makes no sense is to allow multiple ones at the same
time given the current autoconf switches, even if it would just be to pick say
pg_strong_random from one and libpq TLS from another.
Maybe we should just have --with-ssl={openssl,nss}? That'd avoid needing
to check for errors.
Even better, of course, would be to allow switching of the SSL backend
based on config options (PGC_POSTMASTER GUC for backend, connection
string for frontend). Mainly because that would make testing of
interoperability so much easier. Obviously still a few places like
pgcrypto, randomness, etc, where only a compile time decision seems to
make sense.
+ CLEANLDFLAGS="$LDFLAGS" + # TODO: document this set of LDFLAGS + LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"Shouldn't this use nss-config or such?
Indeed it should, where available. I've added rudimentary support for that
without a fallback as of now.
When would we need a fallback?
I think it'd also be better if we could include these files as nss/ssl.h
etc - ssl.h is a name way too likely to conflict imo.I've changed this to be nss/ssl.h and nspr/nspr.h etc, but the include path
will still need the direct path to the headers (from autoconf) since nss.h
includes NSPR headers as #include <nspr.h> and so on.
Hm. Then it's probably not worth going there...
+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.We should, but it's proving rather difficult as there is no equivalent API call
to get the string as well as the expected length of it.
Hm. Should at least have a test to ensure that's not a problem then. I
hope/assume NSS rejects this somewhere internally...
Also, what's up with the CN= bit? Why is that needed here, but not for
openssl?OpenSSL returns only the value portion, whereas NSS returns key=value so we
need to skip over the key= part.
Why is it a conditional path though?
+/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in <private/pprio.h>, but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);Ugh. This is really the way to do this? How do other applications deal
with this problem?They either #include <private/pprio.h> or they do it like this (or vendor NSPR
which makes calling private APIs less problematic). It sure is ugly, but there
is no alternative to using this function.
Hm - in debian unstable's NSS this function appears to be in nss/ssl.h,
not pprio.h:
/*
** Imports fd into SSL, returning a new socket. Copies SSL configuration
** from model.
*/
SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd);
and ssl.h starts with:
/*
* This file contains prototypes for the public SSL functions.
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);Is it actually OK to do stuff like this when other users of NSS might be
present? That's obviously more likely in the libpq case, compared to the
backend case (where it's also possible, of course). What prevents us
from overriding another user's callback?The password callback pointer is stored in a static variable in NSS (in the
file lib/pk11wrap/pk11auth.c).
But, uh, how is that not a problem? What happens if a backend imports
libpq? What if plpython imports curl which then also uses nss?
+ /* + * Finally we must configure the socket for being a server by setting the + * certificate and key. + */ + status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure secure server: %s", + pg_SSLerrmessage(PR_GetError())))); + status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure server for TLS server connections: %s", + pg_SSLerrmessage(PR_GetError()))));
Why do both of these need to get called? The NSS docs say:
/*
** Deprecated variant of SSL_ConfigServerCert.
**
...
SSL_IMPORT SECStatus SSL_ConfigSecureServer(
PRFileDesc *fd, CERTCertificate *cert,
SECKEYPrivateKey *key, SSLKEAType kea);
Greetings,
Andres Freund
Personally I'd like to see this patch broken up a bit - it's quite
large. Several of the changes could easily be committed separately, no?Not sure how much of this makes sense committed separately (unless separately
means in quick succession), but it could certainly be broken up for the sake of
making review easier.Committing e.g. the pgcrypto pieces separately from the backend code
seems unproblematic. But yes, I would expect them to go in close to each
other. I'm mainly concerned with smaller review-able units.
Attached is a v14 where the logical units are separated into individual
commits. I hope this split makes it easier to read.
The 0006 commit were things not really related to NSS at all that can be
submitted to -hackers independently of this work, but they're still there since
this version wasn't supposed to change anything.
Most of the changes to sslinfo in 0005 are really only needed in case OpenSSL
isn't the only TLS library, but I would argue that they should be considered
regardless. There we are still accessing the ->ssl member directly and passing
it to OpenSSL rather than using the be_tls_* API that we have. I can extract
that portion as a separate patch submission unless there are objections.
cheers ./daniel
Attachments:
v14-0001-NSS-Frontend-Backend-and-build-infra.patchapplication/octet-stream; name=v14-0001-NSS-Frontend-Backend-and-build-infra.patch; x-unix-mode=0644Download
From e409e028b8474f8ec8ce4a6aa8148131f5e11386 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v14 1/6] NSS Frontend, Backend and build infra
---
configure | 330 +++++
configure.ac | 41 +
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1175 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 11 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1018 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
24 files changed, 2842 insertions(+), 14 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..48ca9bcafe 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,6 +713,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -857,6 +860,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1559,6 +1563,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8106,6 +8111,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5
+$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12180,6 +12220,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12442,6 +12485,274 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5
+ fi
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13344,6 +13655,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..89fbb5e171 100644
--- a/configure.ac
+++ b/configure.ac
@@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])])
+AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1235,6 +1247,30 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [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])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
fi
@@ -1410,6 +1446,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..8946fa696a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2b184be3d1
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1175 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4c86fb6087..8821c60a34 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..9205f0fc3b
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..7b396d04af 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,9 +45,13 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
+ifeq ($(with_ssl),yes)
+OBJS += \
+ fe-secure-common.o
+endif
+
ifeq ($(with_openssl),yes)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
@@ -57,6 +61,11 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b0ca37c2ed..a15a89c50c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..395147f551
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1018 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ default:
+ read_errno = ECONNRESET;
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..49a22f6c7d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v14-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v14-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 3275f494a8c6669e4e8223ae84c0f06176c632c6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v14 2/6] NSS Testharness updates
---
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 176 ++++++++++
src/test/ssl/t/001_ssltests.pl | 326 ++++++++++--------
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
7 files changed, 602 insertions(+), 153 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 9774f534d9..a09c350939 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..4af971ef77 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +126,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +209,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +237,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +277,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +322,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..ff9c8ae3f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..b50659e568 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..1261d21861 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v14-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v14-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 6b8d1df2ba900085c50ef3e5ec29f11ee3373171 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v14 3/6] NSS pg_strong_random support
---
configure | 12 ++++++++++--
configure.ac | 9 +++++++--
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 37 +++++++++++++++++++++++++++++++++++++
4 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/configure b/configure
index 48ca9bcafe..412f40b58a 100755
--- a/configure
+++ b/configure
@@ -18391,11 +18391,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
@@ -18437,6 +18439,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18446,7 +18454,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 89fbb5e171..86311946d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2199,11 +2199,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_nss" = x"yes" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2220,13 +2222,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..33af92a60b 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v14-0004-NSS-Documentation.patchapplication/octet-stream; name=v14-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 4d00729574d3245c7255bd9e22086bbc9a09b83b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v14 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v14-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v14-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 027cb4c4352a9e2a8309bb1a0c1f7aa567feccbe Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v14 5/6] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 5 +
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +
contrib/sslinfo/sslinfo.c | 164 ++++----
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
10 files changed, 910 insertions(+), 101 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..d0b01cb2cc 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..6c69ee5a6b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,16 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
+CF_SRCS = $(if $(subst no,,$(with_nss)), $(NSS_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_TESTS = $(if $(subst no,,$(with_nss)), $(NSS_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c
index 6a4681dae9..a243f575d3 100644
--- a/contrib/pgcrypto/px.c
+++ b/contrib/pgcrypto/px.c
@@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = {
{PXE_MCRYPT_INTERNAL, "mcrypt internal error"},
{PXE_NO_RANDOM, "Failed to generate strong random bits"},
{PXE_DECRYPT_FAILED, "Decryption failed"},
+ {PXE_ENCRYPT_FAILED, "Encryption failed"},
{PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"},
{PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"},
{PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"},
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 5487923edb..17d6f22498 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -61,6 +61,7 @@
#define PXE_MCRYPT_INTERNAL -16
#define PXE_NO_RANDOM -17
#define PXE_DECRYPT_FAILED -18
+#define PXE_ENCRYPT_FAILED -19
#define PXE_PGP_CORRUPT_DATA -100
#define PXE_PGP_CORRUPT_ARMOR -101
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..e8a4e7b332 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
v14-0006-NSS-to-be-submitted-separately.patchapplication/octet-stream; name=v14-0006-NSS-to-be-submitted-separately.patch; x-unix-mode=0644Download
From f2e9c124ecf695f0434588037ac37592f6f3848e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:35:06 +0100
Subject: [PATCH v14 6/6] NSS to be submitted separately
---
contrib/pgcrypto/openssl.c | 2 +-
doc/src/sgml/sslinfo.sgml | 2 +-
src/backend/libpq/be-secure-openssl.c | 16 +++++++++++++++-
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index ed96e4ce53..5ebe213406 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -400,7 +400,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
}
if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen))
- return PXE_ERR_GENERIC;
+ return PXE_ENCRYPT_FAILED;
return 0;
}
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e8a4e7b332..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -55,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
--
2.21.1 (Apple Git-122.3)
On 28 Oct 2020, at 07:39, Andres Freund <andres@anarazel.de> wrote:
Have you done testing to ensure that NSS PG cooperates correctly with
openssl PG? Is there a way we can make that easier to do? E.g. allowing
to build frontend with NSS and backend with openssl and vice versa?
When I wrote the Secure Transport patch I had a patch against PostgresNode
which allowed for overriding the server binaries like so:
SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
I've used that coupled with manual testing so far to make sure that an openssl
client can talk to an NSS backend and so on. Before any other backend is added
we clearly need *a* way of doing this, one which no doubt will need to be
improved upon to suit more workflows.
This is sort of the same situation as pg_upgrade, where two trees is needed to
really test it.
I can clean that patch up and post as a starting point for discussions.
if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fiBased on a quick look there's no similar error check for the msvc
build. Should there be?Thats a good question. When embarking on this is seemed quite natural to me
that it should be, but now I'm not so sure. Maybe there should be a
--with-openssl-preferred like how we handle readline/libedit or just allow
multiple and let the last one win? Do you have any input on what would make
sense?The only thing I think makes no sense is to allow multiple ones at the same
time given the current autoconf switches, even if it would just be to pick say
pg_strong_random from one and libpq TLS from another.Maybe we should just have --with-ssl={openssl,nss}? That'd avoid needing
to check for errors.
Thats another option, with --with-openssl being an alias for --with-ssl=openssl.
After another round of thinking I like this even better as it makes the build
infra cleaner, so the attached patch has this implemented.
Even better, of course, would be to allow switching of the SSL backend
based on config options (PGC_POSTMASTER GUC for backend, connection
string for frontend). Mainly because that would make testing of
interoperability so much easier. Obviously still a few places like
pgcrypto, randomness, etc, where only a compile time decision seems to
make sense.
It would make testing easier, but the expense seems potentially rather high.
How would a GUC switch be allowed to operate, would we have mixed backends or
would be require all openssl connectins to be dropped before serving nss ones?
+ CLEANLDFLAGS="$LDFLAGS" + # TODO: document this set of LDFLAGS + LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"Shouldn't this use nss-config or such?
Indeed it should, where available. I've added rudimentary support for that
without a fallback as of now.When would we need a fallback?
One one of my boxes I have NSS/NSPR installed via homebrew and they don't ship
an nss-config AFAICT. I wouldn't be surprised if there are other cases.
I think it'd also be better if we could include these files as nss/ssl.h
etc - ssl.h is a name way too likely to conflict imo.I've changed this to be nss/ssl.h and nspr/nspr.h etc, but the include path
will still need the direct path to the headers (from autoconf) since nss.h
includes NSPR headers as #include <nspr.h> and so on.Hm. Then it's probably not worth going there...
It does however make visual parsing of the source files easer since it's clear
which ssl.h is being referred to. I'm in favor of keeping it.
+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.We should, but it's proving rather difficult as there is no equivalent API call
to get the string as well as the expected length of it.Hm. Should at least have a test to ensure that's not a problem then. I
hope/assume NSS rejects this somewhere internally...
Agreed, I'll try to hack up a testcase.
Also, what's up with the CN= bit? Why is that needed here, but not for
openssl?OpenSSL returns only the value portion, whereas NSS returns key=value so we
need to skip over the key= part.Why is it a conditional path though?
It was mostly just a belts-and-suspenders thing, I don't have any hard evidence
that it's been a thing in any modern NSS version so it can be removed.
+/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in <private/pprio.h>, but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);Ugh. This is really the way to do this? How do other applications deal
with this problem?They either #include <private/pprio.h> or they do it like this (or vendor NSPR
which makes calling private APIs less problematic). It sure is ugly, but there
is no alternative to using this function.Hm - in debian unstable's NSS this function appears to be in nss/ssl.h,
not pprio.h:/*
** Imports fd into SSL, returning a new socket. Copies SSL configuration
** from model.
*/
SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd);and ssl.h starts with:
/*
* This file contains prototypes for the public SSL functions.
Right, but that's Import*FD*, not Import*TCPSocket*. We use ImportFD as well
since it's the API for importing an NSPR socket into NSS and enabling SSL/TLS
on it. Thats been a public API for a long time. ImportTCPSocket is used to
import an already opened socket into NSPR, else NSPR must open the socket
itself. That part has been kept private for reasons unknown, as it's
incredibly useful.
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
Is it actually OK to do stuff like this when other users of NSS might be
present? That's obviously more likely in the libpq case, compared to the
backend case (where it's also possible, of course). What prevents us
from overriding another user's callback?The password callback pointer is stored in a static variable in NSS (in the
file lib/pk11wrap/pk11auth.c).But, uh, how is that not a problem? What happens if a backend imports
libpq? What if plpython imports curl which then also uses nss?
Sorry, that sentence wasn't really finished. What I meant to write was that I
don't really have good answers here. The available implementation is via the
static var, and there are no alternative APIs. I've tried googling for
insights but haven't come across any.
The only datapoint I have is that I can't recall there ever being a complaint
against libcurl doing this exact thing. That of course doesn't mean it cannot
happen or cause problems.
+ /* + * Finally we must configure the socket for being a server by setting the + * certificate and key. + */ + status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure secure server: %s", + pg_SSLerrmessage(PR_GetError())))); + status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure server for TLS server connections: %s", + pg_SSLerrmessage(PR_GetError()))));Why do both of these need to get called? The NSS docs say:
/*
** Deprecated variant of SSL_ConfigServerCert.
**
...
SSL_IMPORT SECStatus SSL_ConfigSecureServer(
PRFileDesc *fd, CERTCertificate *ce rt,
SECKEYPrivateKey *key, SSLKEAType kea);
They don't, I had missed the deprecation warning as it's not mentioned at all
in the online documentation:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/SSL_functions/sslfnc.html
(SSL_ConfigServerCert isn't at all mentioned there which dates it to before
this went it obsoleting SSL_ConfigSecureServer.)
Fixed by removing the superfluous call.
Thanks again for reviewing!
cheers ./daniel
Attachments:
v15-0001-NSS-Frontend-Backend-and-build-infra.patchapplication/octet-stream; name=v15-0001-NSS-Frontend-Backend-and-build-infra.patch; x-unix-mode=0644Download
From 67efed92f5b60196066b79cae1fdb775f0edd785 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v15 1/6] NSS Frontend, Backend and build infra
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1173 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1018 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 2871 insertions(+), 71 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..6287a0e2fe 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..706c9862e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..b26a4bd620
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1173 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4c86fb6087..8821c60a34 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..9205f0fc3b
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b0ca37c2ed..a15a89c50c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..395147f551
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1018 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ default:
+ read_errno = ECONNRESET;
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..49a22f6c7d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v15-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v15-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 312e98aa96ba91a5c0d07481972c34754d0d44f4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v15 2/6] NSS Testharness updates
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 608 insertions(+), 160 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 9774f534d9..a09c350939 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..e8c0db9a95 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d $@ -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..b4d4483fb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..9c7e1ac977 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;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v15-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v15-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From d9a150702d9441e043ede97445713efc5401257f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v15 3/6] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 37 +++++++++++++++++++++++++++++++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index 6287a0e2fe..f78772bd42 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 706c9862e7..01c23356ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..33af92a60b 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v15-0004-NSS-Documentation.patchapplication/octet-stream; name=v15-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 8f5cf6930875caa48f70c3982b9d176beb2f6e86 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v15 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v15-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v15-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 752805f50dd7c63b8784cd15336ad3056d98adf9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v15 5/6] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/pgcrypto/px.c | 1 +
contrib/pgcrypto/px.h | 1 +
contrib/sslinfo/sslinfo.c | 164 ++++----
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
10 files changed, 910 insertions(+), 103 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c
index 6a4681dae9..a243f575d3 100644
--- a/contrib/pgcrypto/px.c
+++ b/contrib/pgcrypto/px.c
@@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = {
{PXE_MCRYPT_INTERNAL, "mcrypt internal error"},
{PXE_NO_RANDOM, "Failed to generate strong random bits"},
{PXE_DECRYPT_FAILED, "Decryption failed"},
+ {PXE_ENCRYPT_FAILED, "Encryption failed"},
{PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"},
{PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"},
{PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"},
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 5487923edb..17d6f22498 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -61,6 +61,7 @@
#define PXE_MCRYPT_INTERNAL -16
#define PXE_NO_RANDOM -17
#define PXE_DECRYPT_FAILED -18
+#define PXE_ENCRYPT_FAILED -19
#define PXE_PGP_CORRUPT_DATA -100
#define PXE_PGP_CORRUPT_ARMOR -101
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..e8a4e7b332 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
v15-0006-NSS-to-be-submitted-separately.patchapplication/octet-stream; name=v15-0006-NSS-to-be-submitted-separately.patch; x-unix-mode=0644Download
From ef84f48a34d3076c4706b414033f1a388be8c990 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:35:06 +0100
Subject: [PATCH v15 6/6] NSS to be submitted separately
---
contrib/pgcrypto/openssl.c | 2 +-
doc/src/sgml/sslinfo.sgml | 2 +-
src/backend/libpq/be-secure-openssl.c | 16 +++++++++++++++-
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index ed96e4ce53..5ebe213406 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -400,7 +400,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
}
if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen))
- return PXE_ERR_GENERIC;
+ return PXE_ENCRYPT_FAILED;
return 0;
}
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e8a4e7b332..253bb697af 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -55,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
--
2.21.1 (Apple Git-122.3)
On 10/29/20 11:20 AM, Daniel Gustafsson wrote:
On 28 Oct 2020, at 07:39, Andres Freund <andres@anarazel.de> wrote:
Have you done testing to ensure that NSS PG cooperates correctly with
openssl PG? Is there a way we can make that easier to do? E.g. allowing
to build frontend with NSS and backend with openssl and vice versa?When I wrote the Secure Transport patch I had a patch against PostgresNode
which allowed for overriding the server binaries like so:SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
I've used that coupled with manual testing so far to make sure that an openssl
client can talk to an NSS backend and so on. Before any other backend is added
we clearly need *a* way of doing this, one which no doubt will need to be
improved upon to suit more workflows.This is sort of the same situation as pg_upgrade, where two trees is needed to
really test it.I can clean that patch up and post as a starting point for discussions.
if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fiBased on a quick look there's no similar error check for the msvc
build. Should there be?Thats a good question. When embarking on this is seemed quite natural to me
that it should be, but now I'm not so sure. Maybe there should be a
--with-openssl-preferred like how we handle readline/libedit or just allow
multiple and let the last one win? Do you have any input on what would make
sense?The only thing I think makes no sense is to allow multiple ones at the same
time given the current autoconf switches, even if it would just be to pick say
pg_strong_random from one and libpq TLS from another.Maybe we should just have --with-ssl={openssl,nss}? That'd avoid needing
to check for errors.Thats another option, with --with-openssl being an alias for --with-ssl=openssl.
After another round of thinking I like this even better as it makes the build
infra cleaner, so the attached patch has this implemented.Even better, of course, would be to allow switching of the SSL backend
based on config options (PGC_POSTMASTER GUC for backend, connection
string for frontend). Mainly because that would make testing of
interoperability so much easier. Obviously still a few places like
pgcrypto, randomness, etc, where only a compile time decision seems to
make sense.It would make testing easier, but the expense seems potentially rather high.
How would a GUC switch be allowed to operate, would we have mixed backends or
would be require all openssl connectins to be dropped before serving nss ones?+ CLEANLDFLAGS="$LDFLAGS" + # TODO: document this set of LDFLAGS + LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"Shouldn't this use nss-config or such?
Indeed it should, where available. I've added rudimentary support for that
without a fallback as of now.When would we need a fallback?
One one of my boxes I have NSS/NSPR installed via homebrew and they don't ship
an nss-config AFAICT. I wouldn't be surprised if there are other cases.I think it'd also be better if we could include these files as nss/ssl.h
etc - ssl.h is a name way too likely to conflict imo.I've changed this to be nss/ssl.h and nspr/nspr.h etc, but the include path
will still need the direct path to the headers (from autoconf) since nss.h
includes NSPR headers as #include <nspr.h> and so on.Hm. Then it's probably not worth going there...
It does however make visual parsing of the source files easer since it's clear
which ssl.h is being referred to. I'm in favor of keeping it.+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.We should, but it's proving rather difficult as there is no equivalent API call
to get the string as well as the expected length of it.Hm. Should at least have a test to ensure that's not a problem then. I
hope/assume NSS rejects this somewhere internally...Agreed, I'll try to hack up a testcase.
Also, what's up with the CN= bit? Why is that needed here, but not for
openssl?OpenSSL returns only the value portion, whereas NSS returns key=value so we
need to skip over the key= part.Why is it a conditional path though?
It was mostly just a belts-and-suspenders thing, I don't have any hard evidence
that it's been a thing in any modern NSS version so it can be removed.+/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in <private/pprio.h>, but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);Ugh. This is really the way to do this? How do other applications deal
with this problem?They either #include <private/pprio.h> or they do it like this (or vendor NSPR
which makes calling private APIs less problematic). It sure is ugly, but there
is no alternative to using this function.Hm - in debian unstable's NSS this function appears to be in nss/ssl.h,
not pprio.h:/*
** Imports fd into SSL, returning a new socket. Copies SSL configuration
** from model.
*/
SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd);and ssl.h starts with:
/*
* This file contains prototypes for the public SSL functions.Right, but that's Import*FD*, not Import*TCPSocket*. We use ImportFD as well
since it's the API for importing an NSPR socket into NSS and enabling SSL/TLS
on it. Thats been a public API for a long time. ImportTCPSocket is used to
import an already opened socket into NSPR, else NSPR must open the socket
itself. That part has been kept private for reasons unknown, as it's
incredibly useful.+ PK11_SetPasswordFunc(PQssl_passwd_cb);
Is it actually OK to do stuff like this when other users of NSS might be
present? That's obviously more likely in the libpq case, compared to the
backend case (where it's also possible, of course). What prevents us
from overriding another user's callback?The password callback pointer is stored in a static variable in NSS (in the
file lib/pk11wrap/pk11auth.c).But, uh, how is that not a problem? What happens if a backend imports
libpq? What if plpython imports curl which then also uses nss?Sorry, that sentence wasn't really finished. What I meant to write was that I
don't really have good answers here. The available implementation is via the
static var, and there are no alternative APIs. I've tried googling for
insights but haven't come across any.The only datapoint I have is that I can't recall there ever being a complaint
against libcurl doing this exact thing. That of course doesn't mean it cannot
happen or cause problems.+ /* + * Finally we must configure the socket for being a server by setting the + * certificate and key. + */ + status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure secure server: %s", + pg_SSLerrmessage(PR_GetError())))); + status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure server for TLS server connections: %s", + pg_SSLerrmessage(PR_GetError()))));Why do both of these need to get called? The NSS docs say:
/*
** Deprecated variant of SSL_ConfigServerCert.
**
...
SSL_IMPORT SECStatus SSL_ConfigSecureServer(
PRFileDesc *fd, CERTCertificate *ce rt,
SECKEYPrivateKey *key, SSLKEAType kea);They don't, I had missed the deprecation warning as it's not mentioned at all
in the online documentation:https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/SSL_functions/sslfnc.html
(SSL_ConfigServerCert isn't at all mentioned there which dates it to before
this went it obsoleting SSL_ConfigSecureServer.)Fixed by removing the superfluous call.
I've been looking through the new patch set, in particular the testing
setup.
The way it seems to proceed is to use the existing openssl generated
certificates and imports them into NSS certificate databases. That seems
fine to bootstrap testing, but it seems to me it would be more sound not
to rely on openssl at all. I'd rather see the Makefile containing
commands to create these from scratch, which mirror the openssl
variants. IOW you should be able to build and test this from scratch,
including certificate generation, without having openssl installed at all.
I also notice that the invocations to pk12util don't contain the "sql:"
prefix to the -d option, even though the database was created with that
prefix a few lines above. That seems like a mistake from my reading of
the pk12util man page.
cheers
andrew
On 1 Nov 2020, at 14:13, Andrew Dunstan <andrew@dunslane.net> wrote:
I've been looking through the new patch set, in particular the testing
setup.
Thanks!
The way it seems to proceed is to use the existing openssl generated
certificates and imports them into NSS certificate databases. That seems
fine to bootstrap testing,
That's pretty much why I opted for using the existing certs: to bootstrap the
patch and ensure OpenSSL-backend compatibility.
but it seems to me it would be more sound not
to rely on openssl at all. I'd rather see the Makefile containing
commands to create these from scratch, which mirror the openssl
variants. IOW you should be able to build and test this from scratch,
including certificate generation, without having openssl installed at all.
I don't disagree with this, but I do also believe there is value in testing all
TLS backends with exactly the same certificates to act as a baseline. The
nssfiles target should definitely be able to generate from scratch, but maybe a
combination is the best option?
Being well versed in the buildfarm code, do you have an off-the-cuff idea on
how to do cross library testing such that OpenSSL/NSS compatibility can be
ensured? Andres was floating the idea of making a single sourcetree be able to
have both for testing but more discussion is needed to settle on a way forward.
I also notice that the invocations to pk12util don't contain the "sql:"
prefix to the -d option, even though the database was created with that
prefix a few lines above. That seems like a mistake from my reading of
the pk12util man page.
Fixed in the attached v16, which also drops the parts of the patchset which
have been submitted separately to -hackers (the sslinfo patch hunks are still
there are they are required).
cheers ./daniel
Attachments:
v16-0001-NSS-Frontend-Backend-and-build-infra.patchapplication/octet-stream; name=v16-0001-NSS-Frontend-Backend-and-build-infra.patch; x-unix-mode=0644Download
From 850e4ef964a5af1417ee97eb0fd7167a38f4eaa7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v16 1/5] NSS Frontend, Backend and build infra
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1173 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 141 ++
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1018 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 2871 insertions(+), 71 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..6287a0e2fe 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..706c9862e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..b26a4bd620
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1173 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4c86fb6087..8821c60a34 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..9205f0fc3b
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..b68ef64ec9 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b0ca37c2ed..a15a89c50c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..395147f551
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1018 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so 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.
+ */
+static bool has_password = false;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ default:
+ read_errno = ECONNRESET;
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_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 (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..49a22f6c7d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v16-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v16-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From afc3705b2edf645b3e8789073f1302963c4f2251 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v16 2/5] NSS Testharness updates
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 608 insertions(+), 160 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 9774f534d9..a09c350939 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..3e0ac72a06 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..b4d4483fb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..9c7e1ac977 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;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v16-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v16-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From e6d64a38c864c3355585547219b58db7c21e5c78 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v16 3/5] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 37 +++++++++++++++++++++++++++++++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index 6287a0e2fe..f78772bd42 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 706c9862e7..01c23356ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..33af92a60b 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v16-0004-NSS-Documentation.patchapplication/octet-stream; name=v16-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 909e1181ec817618eef1bb88f507628bd471ea72 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v16 4/5] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v16-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v16-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From a329224d8626931422c78833c508f9cb6dce5797 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v16 5/5] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 164 ++++----
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 908 insertions(+), 103 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
@@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index e16f61b41d..e8a4e7b332 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
On 11/1/20 5:04 PM, Daniel Gustafsson wrote:
On 1 Nov 2020, at 14:13, Andrew Dunstan <andrew@dunslane.net> wrote:
I've been looking through the new patch set, in particular the testing
setup.Thanks!
The way it seems to proceed is to use the existing openssl generated
certificates and imports them into NSS certificate databases. That seems
fine to bootstrap testing,That's pretty much why I opted for using the existing certs: to bootstrap the
patch and ensure OpenSSL-backend compatibility.but it seems to me it would be more sound not
to rely on openssl at all. I'd rather see the Makefile containing
commands to create these from scratch, which mirror the openssl
variants. IOW you should be able to build and test this from scratch,
including certificate generation, without having openssl installed at all.I don't disagree with this, but I do also believe there is value in testing all
TLS backends with exactly the same certificates to act as a baseline. The
nssfiles target should definitely be able to generate from scratch, but maybe a
combination is the best option?
Yeah. I certainly think we need something that should how we would
generate them from scratch using nss. That said, the importation code is
also useful.
Being well versed in the buildfarm code, do you have an off-the-cuff idea onIU
how to do cross library testing such that OpenSSL/NSS compatibility can be
ensured? Andres was floating the idea of making a single sourcetree be able to
have both for testing but more discussion is needed to settle on a way forward.
Well, I'd probably try to leverage the knowledge we have in doing
cross-version upgrade testing. It works like this: After the
install-check-C stage each branch saves its binaries and data files in a
special location, adjusting things like library locations to match. then
to test that version it uses that against all the older versions
similarly saved.
We could generalize that saving mechanism and do it if any module
required it. But instead of testing against a different branch, we'd
test against a different animal. So we'd have two animals, one building
with openssl and one with nss, and they would test against each other
(i.e. one as the client and one as the sever, and vice versa).
This would involve a deal of work on my part, but it's very doable, I
believe.
We'd need a way to run tests where we could specify the client and
server binary locations.
Anyway, those are my thoughts. Comments welcome.
cheers
andrew
--
Andrew Dunstan
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 27 Oct 2020, at 21:18, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 27/10/2020 22:07, Daniel Gustafsson wrote:
/*
* Track whether the NSS database has a password set or not. There is no API
* function for retrieving password status, so 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.
*/
static bool has_password = false;This is set in PQssl_passwd_cb function, but never reset. That seems wrong. The NSS database used in one connection might have a password, while another one might not. Or have I completely misunderstood this?
(sorry for slow response). You are absolutely right, the has_password flag
must be tracked per connection in PGconn. The attached v17 implements this as
well a frontend bugfix which caused dropped connections and some smaller fixups
to make strings more translateable.
I've also included a WIP version of SCRAM channel binding in the attached
patch, it's currently failing to connect but someone here might spot the bug
before I do so I figured it's better to include it.
The 0005 patch is now, thanks to the sslinfo patch going in on master, only
containing NSS specific code.
cheers ./daniel
Attachments:
v17-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v17-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From c0179ee27513ef22b828680590d84149bca7c2d1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v17 1/5] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1228 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1080 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 18 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3035 insertions(+), 73 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..6287a0e2fe 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5
+$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5
+$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; }
+if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..706c9862e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..755c7c5219
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1228 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+ /* Set sha-256 as a default */
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pstrdup((char *) digest.data);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3a78d2043e..eb1b028464 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..26c767c391
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..74f88b628e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e7781d010f..8188401659 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..fbddba8083
--- /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-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_strdup((char *) digest.data);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..5d9326490a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,21 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is that
+ * there are calls which require a password parameter, but doesn't use the
+ * callbacks provided, so we must call the callback on behalf of these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -755,7 +771,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v17-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v17-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 7e7a6356eb05ac03256d8f8cb5e93607e1bcd1e3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v17 2/5] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 8 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 611 insertions(+), 159 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 14cde4f5ba..1b6876ca18 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..3e0ac72a06 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..b4d4483fb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..dce18424ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,12 +11,16 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'channel binding not supported with NSS';
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v17-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v17-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 9d0a331d7f6aa5f9a07ca050a201a7c9170cde7c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v17 3/5] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 37 +++++++++++++++++++++++++++++++++++++
4 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index 6287a0e2fe..f78772bd42 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 706c9862e7..01c23356ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 14e8382cd8..33af92a60b 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,20 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v17-0004-NSS-Documentation.patchapplication/octet-stream; name=v17-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 40d18b8072cf278134f1478aec66a39cbef22201 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v17 4/5] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v17-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v17-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 358a594e87ac5cac67d7943832ef7f84459200b8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v17 5/5] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
On 2 Nov 2020, at 15:17, Andrew Dunstan <andrew@dunslane.net> wrote:
We could generalize that saving mechanism and do it if any module
required it. But instead of testing against a different branch, we'd
test against a different animal. So we'd have two animals, one building
with openssl and one with nss, and they would test against each other
(i.e. one as the client and one as the sever, and vice versa).
That seems like a very good plan. It would also allow us to test a backend
compiled with OpenSSL 1.0.2 against a frontend with OpenSSL 1.1.1 which might
come in handy when OpenSSL 3.0.0 lands.
This would involve a deal of work on my part, but it's very doable, I
believe.
I have no experience with the buildfarm code, but I'm happy to help if theres
anything I can do.
cheers ./daniel
On Nov 4, 2020, at 5:09 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
(sorry for slow response). You are absolutely right, the has_password flag
must be tracked per connection in PGconn. The attached v17 implements this as
well a frontend bugfix which caused dropped connections and some smaller fixups
to make strings more translateable.
Some initial notes from building and testing on macOS Mojave. I'm working with
both a brew-packaged NSS/NSPR (which includes basic nss-/nspr-config) and a
hand-built NSS/NSPR (which does not).
1. In configure.ac:
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
Looks like SSL_VersionRangeSet is part of libssl3, not libnss3. So this fails
with the hand-built stack, where there is no nss-config to populate LDFLAGS. I
changed the function to NSS_InitContext and that seems to work nicely.
2. Among the things to eventually think about when it comes to configuring, it
looks like some platforms [1]https://github.com/erthink/ReOpenLDAP/issues/112 install the headers under <nspr4/...> and
<nss3/...> instead of <nspr/...> and <nss/...>. It's unfortunate that the NSS
maintainers never chose an official installation layout.
3. I need two more `#define NO_NSPR_10_SUPPORT` guards added in both
src/include/common/pg_nss.h
src/port/pg_strong_random.c
before the tree will compile for me. Both of those files include NSS headers.
4. be_tls_init() refuses to run correctly for me; I end up getting an NSPR
assertion that looks like
sslMutex_Init not implemented for multi-process applications !
With assertions disabled, this ends up showing a somewhat unhelpful
FATAL: unable to set up TLS connection cache: security library failure. (SEC_ERROR_LIBRARY_FAILURE)
It looks like cross-process locking isn't actually enabled on macOS, which is a
long-standing bug in NSPR [2, 3]. So calls to SSL_ConfigMPServerSIDCache()
error out.
--Jacob
[1]: https://github.com/erthink/ReOpenLDAP/issues/112
[2]: https://bugzilla.mozilla.org/show_bug.cgi?id=538680
[3]: https://bugzilla.mozilla.org/show_bug.cgi?id=1192500
On 6 Nov 2020, at 21:37, Jacob Champion <pchampion@vmware.com> wrote:
Some initial notes from building and testing on macOS Mojave. I'm working with
both a brew-packaged NSS/NSPR (which includes basic nss-/nspr-config) and a
hand-built NSS/NSPR (which does not).
Thanks for looking!
1. In configure.ac:
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])Looks like SSL_VersionRangeSet is part of libssl3, not libnss3. So this fails
with the hand-built stack, where there is no nss-config to populate LDFLAGS. I
changed the function to NSS_InitContext and that seems to work nicely.
Ah yes, fixed.
2. Among the things to eventually think about when it comes to configuring, it
looks like some platforms [1] install the headers under <nspr4/...> and
<nss3/...> instead of <nspr/...> and <nss/...>. It's unfortunate that the NSS
maintainers never chose an official installation layout.
Yeah, maybe we need to start with the most common path and have fallbacks in
case not found?
3. I need two more `#define NO_NSPR_10_SUPPORT` guards added in both
src/include/common/pg_nss.h
src/port/pg_strong_random.cbefore the tree will compile for me. Both of those files include NSS headers.
Odd that I was able to compile on Linux, but I've added these.
4. be_tls_init() refuses to run correctly for me; I end up getting an NSPR
assertion that looks likesslMutex_Init not implemented for multi-process applications !
With assertions disabled, this ends up showing a somewhat unhelpful
FATAL: unable to set up TLS connection cache: security library failure. (SEC_ERROR_LIBRARY_FAILURE)
It looks like cross-process locking isn't actually enabled on macOS, which is a
long-standing bug in NSPR [2, 3]. So calls to SSL_ConfigMPServerSIDCache()
error out.
Thats unfortunate since the session cache is required for a server application
backed by NSS. The attached switches to SSL_ConfigServerSessionIDCacheWithOpt
with which one can explicitly make the cache non-shared, which in turn backs
the mutexes with NSPR locks rather than the missing sem_init. Can you test
this version and see if that makes it work?
This version also contains a channel binding bug that Heikki pointed out off-
list (sadly not The bug) and a few very minor cleanups as well as a rebase to
handle the new pg_strong_random_init. Actually performing the context init
there is yet a TODO, but I wanted a version out that at all compiled.
cheers ./daniel
Attachments:
v18-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v18-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From 0d9f8f0427880c489890bbef813bd012b9c438c5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v18 1/5] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1193 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1081 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 18 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3001 insertions(+), 73 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..d99ec29c6a 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..7b9f4b10a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..86ac400782
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1193 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * 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 aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+ /* Set sha-256 as a default */
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3a78d2043e..eb1b028464 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..74f88b628e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e7781d010f..8188401659 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..3090f37f5e
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1081 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ 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 wont block.
+ */
+ n = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..5d9326490a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,21 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is that
+ * there are calls which require a password parameter, but doesn't use the
+ * callbacks provided, so we must call the callback on behalf of these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -755,7 +771,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v18-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v18-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 0d4c82bf4824183ac88345c0005b9f96e1812780 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v18 2/5] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 8 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 611 insertions(+), 159 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 14cde4f5ba..1b6876ca18 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..3e0ac72a06 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..b4d4483fb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 20ab0d5b0b..dce18424ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,12 +11,16 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'channel binding not supported with NSS';
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v18-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v18-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 811174d7304c6151ad5678d766f0c67c7a8a8a84 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v18 3/5] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 40 +++++++++++++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index d99ec29c6a..c15f8953ee 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 7b9f4b10a3..01b0ff98b3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 6d85f50b7c..ed7be98212 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,21 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -114,6 +129,8 @@ pg_strong_random_init(void)
#elif defined(USE_DEV_URANDOM)
/* no initialization needed for /dev/urandom */
+#elif defined(USE_NSS_RANDOM)
+ /* TODO: set up NSS context here */
#else
#error no source of random numbers configured
#endif
@@ -202,6 +219,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v18-0004-NSS-Documentation.patchapplication/octet-stream; name=v18-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 84a4c02e323139da0adef3113f16bd36ac5d3364 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v18 4/5] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9ce32fb39b..61d9082052 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..fe7e305b75 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The paramaters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v18-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v18-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 020e18ab567f11d41314659d8bbd9c34ff760c02 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v18 5/5] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..e1291ed403
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 publically accessible array which can be scanned
+ * for the name.
+ */
+ for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
On Nov 6, 2020, at 3:11 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
The attached switches to SSL_ConfigServerSessionIDCacheWithOpt
with which one can explicitly make the cache non-shared, which in turn backs
the mutexes with NSPR locks rather than the missing sem_init. Can you test
this version and see if that makes it work?
Yep, I get much farther through the tests with that patch. I'm currently
diving into another assertion failure during socket disconnection:
Assertion failure: fd->secret == NULL, at prlayer.c:45
cURL has some ominously vague references to this [1]https://github.com/curl/curl/blob/4d2f800/lib/vtls/nss.c#L1266, though I'm not
sure that we should work around it in the same way without knowing what
the cause is...
--Jacob
[1]: https://github.com/curl/curl/blob/4d2f800/lib/vtls/nss.c#L1266
On 10 Nov 2020, at 21:11, Jacob Champion <pchampion@vmware.com> wrote:
On Nov 6, 2020, at 3:11 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
The attached switches to SSL_ConfigServerSessionIDCacheWithOpt
with which one can explicitly make the cache non-shared, which in turn backs
the mutexes with NSPR locks rather than the missing sem_init. Can you test
this version and see if that makes it work?Yep, I get much farther through the tests with that patch.
Great, thanks for confirming.
I'm currently
diving into another assertion failure during socket disconnection:Assertion failure: fd->secret == NULL, at prlayer.c:45
cURL has some ominously vague references to this [1], though I'm not
sure that we should work around it in the same way without knowing what
the cause is...
Digging through the archives from when this landed in curl, the assertion
failure was never fully identified back then but happened spuriously. Which
version of NSPR is this happening with?
cheers ./daniel
On Nov 10, 2020, at 2:28 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
Digging through the archives from when this landed in curl, the assertion
failure was never fully identified back then but happened spuriously. Which
version of NSPR is this happening with?
This is NSPR 4.29, with debugging enabled. The fd that causes the
assertion is the custom layer that's added during be_tls_open_server(),
which connects a Port as the layer secret. It looks like NSPR is trying
to help surface potential memory leaks by asserting if the secret is
non-NULL at the time the stack is being closed.
In this case, it doesn't matter since the Port lifetime is managed
elsewhere, but it looks easy enough to add a custom close in the way
that cURL and the NSPR test programs [1]https://hg.mozilla.org/projects/nspr/file/bf6620c143/pr/tests/nblayer.c#l354 do. Sample patch attached,
which gets me to the end of the tests without any assertions. (Two
failures left on my machine.)
--Jacob
[1]: https://hg.mozilla.org/projects/nspr/file/bf6620c143/pr/tests/nblayer.c#l354
Attachments:
patchapplication/octet-stream; name=patchDownload
From e18895eb33b3ab6c66c1cd461061096784a62e3a Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Wed, 11 Nov 2020 09:56:48 -0800
Subject: [PATCH] init_iolayer: manage fd->secret during close
In debug builds, NSPR's default close implementation for descriptors
checks to make sure that any private fd->secret has been NULLed out,
presumably to prove that NSPR does not leak the last reference to
allocated memory. Add a custom close implementation to handle the Port
that is connected to the fd.
---
src/backend/libpq/be-secure-nss.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
index 86ac400782..76523e9870 100644
--- a/src/backend/libpq/be-secure-nss.c
+++ b/src/backend/libpq/be-secure-nss.c
@@ -477,9 +477,6 @@ be_tls_open_server(Port *port)
if (!layer)
goto error;
- /* Store the Port as private data available in callbacks */
- layer->secret = (void *) port;
-
if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
{
PR_Close(layer);
@@ -1052,6 +1049,17 @@ pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
return n_write;
}
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
static PRFileDesc *
init_iolayer(Port *port)
{
@@ -1068,6 +1076,7 @@ init_iolayer(Port *port)
pr_iomethods.recv = pg_ssl_read;
pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
/*
* Each IO layer must be identified by a unique name, where uniqueness is
@@ -1096,6 +1105,9 @@ init_iolayer(Port *port)
return NULL;
}
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
return layer;
}
--
2.24.1
On Nov 11, 2020, at 10:17 AM, Jacob Champion <pchampion@vmware.com> wrote:
(Two failures left on my machine.)
False alarm -- the stderr debugging I'd added in to track down the
assertion tripped up the "no stderr" tests. Zero failing tests now.
--Jacob
On Nov 11, 2020, at 10:57 AM, Jacob Champion <pchampion@vmware.com> wrote:
False alarm -- the stderr debugging I'd added in to track down the
assertion tripped up the "no stderr" tests. Zero failing tests now.
I took a look at the OpenSSL interop problems you mentioned upthread. I
don't see a hang like you did, but I do see a PR_IO_TIMEOUT_ERROR during
connection.
I think pgtls_read() needs to treat PR_IO_TIMEOUT_ERROR as if no bytes
were read, in order to satisfy its API. There was some discussion on
this upthread:
On Oct 27, 2020, at 1:07 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 20 Oct 2020, at 21:15, Andres Freund <andres@anarazel.de> wrote:
+ case PR_IO_TIMEOUT_ERROR:
+ break;What does this mean? We'll return with a 0 errno here, right? When is
this case reachable?It should, AFAICT, only be reachable when PR_Recv is used with a timeout which
we don't do. It mentioned somewhere that it had happened in no-wait calls due
to a bug, but I fail to find that reference now. Either way, I've removed it
to fall into the default error handling which now sets errno correctly as that
was a paddle short here.
PR_IO_TIMEOUT_ERROR is definitely returned in no-wait calls on my
machine. It doesn't look like the PR_Recv() API has a choice -- if
there's no data, it can't return a positive integer, and returning zero
means that the socket has been disconnected. So -1 with a timeout error
is the only option.
I'm not completely sure why this is exposed so easily with an OpenSSL
server -- I'm guessing the implementation slices up its packets
differently on the wire, causing a read event before NSS is able to
decrypt a full record -- but it's worth noting that this case also shows
up during NSS-to-NSS psql connections, when handling notifications at
the end of every query. PQconsumeInput() reports a hard failure with the
current implementation, but its return value is ignored by
PrintNotifications(). Otherwise this probably would have showed up
earlier.
(What's the best way to test this case? Are there lower-level tests for
the protocol/network layer somewhere that I'm missing?)
While patching this case, I also noticed that pgtls_read() doesn't call
SOCK_ERRNO_SET() for the disconnection case. That is also in the
attached patch.
--Jacob
Attachments:
nss-handle-timeouts-and-disconnections-in-pgtls_read.patchapplication/octet-stream; name=nss-handle-timeouts-and-disconnections-in-pgtls_read.patchDownload
From 4bc5dc61e8be490b013be5f9b3326faae2dc99c6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Thu, 12 Nov 2020 12:02:39 -0800
Subject: [PATCH] nss: handle timeouts and disconnections in pgtls_read
PR_IO_TIMEOUT_ERROR was being treated as a hard error, but since we're
explicitly asking PR_Recv() not to wait, we want to treat it as "zero
bytes read" instead.
This case happens relatively often in psql, during PQconsumeInput() --
but the return value of PQconsumeInput() is being ignored during
notification handling, which masks the problem. This also seems to fix
interoperability with OpenSSL servers on my machine, possibly due to
differences in message fragmentation between the implementations?
Also ensure that SOCK_ERRNO_SET() is called in the disconnection case;
read_errno was being set but not used.
---
src/interfaces/libpq/fe-secure-nss.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 3090f37f5e..8fc8e138e2 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -429,15 +429,19 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
if (nread == 0)
{
read_errno = ECONNRESET;
- return -1;
+ nread = -1;
}
-
- if (nread == -1)
+ else if (nread == -1)
{
status = PR_GetError();
switch (status)
{
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
case PR_WOULD_BLOCK_ERROR:
read_errno = EWOULDBLOCK;
break;
@@ -456,9 +460,12 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
break;
}
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("TLS read error: %s"),
- pg_SSLerrmessage(status));
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
}
SOCK_ERRNO_SET(read_errno);
--
2.24.1
On 12 Nov 2020, at 23:12, Jacob Champion <pchampion@vmware.com> wrote:
On Nov 11, 2020, at 10:57 AM, Jacob Champion <pchampion@vmware.com> wrote:
False alarm -- the stderr debugging I'd added in to track down the
assertion tripped up the "no stderr" tests. Zero failing tests now.I took a look at the OpenSSL interop problems you mentioned upthread.
Great, thanks!
I don't see a hang like you did, but I do see a PR_IO_TIMEOUT_ERROR during
connection.I think pgtls_read() needs to treat PR_IO_TIMEOUT_ERROR as if no bytes
were read, in order to satisfy its API. There was some discussion on
this upthread:On Oct 27, 2020, at 1:07 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 20 Oct 2020, at 21:15, Andres Freund <andres@anarazel.de> wrote:
+ case PR_IO_TIMEOUT_ERROR:
+ break;What does this mean? We'll return with a 0 errno here, right? When is
this case reachable?It should, AFAICT, only be reachable when PR_Recv is used with a timeout which
we don't do. It mentioned somewhere that it had happened in no-wait calls due
to a bug, but I fail to find that reference now. Either way, I've removed it
to fall into the default error handling which now sets errno correctly as that
was a paddle short here.PR_IO_TIMEOUT_ERROR is definitely returned in no-wait calls on my
machine. It doesn't look like the PR_Recv() API has a choice -- if
there's no data, it can't return a positive integer, and returning zero
means that the socket has been disconnected. So -1 with a timeout error
is the only option.
Right, that makes sense.
I'm not completely sure why this is exposed so easily with an OpenSSL
server -- I'm guessing the implementation slices up its packets
differently on the wire, causing a read event before NSS is able to
decrypt a full record -- but it's worth noting that this case also shows
up during NSS-to-NSS psql connections, when handling notifications at
the end of every query. PQconsumeInput() reports a hard failure with the
current implementation, but its return value is ignored by
PrintNotifications(). Otherwise this probably would have showed up
earlier.
Should there perhaps be an Assert there to catch those?
(What's the best way to test this case? Are there lower-level tests for
the protocol/network layer somewhere that I'm missing?)
Not AFAIK. Having been knee-deep now, do you have any ideas on how to
implement?
While patching this case, I also noticed that pgtls_read() doesn't call
SOCK_ERRNO_SET() for the disconnection case. That is also in the
attached patch.
Ah yes, nice catch.
I've incorporated this patch as well as the previous patch for the assertion
failure on private callback data into the attached v19 patchset. I also did a
spellcheck and pgindent run on it for ease of review.
cheers ./daniel
Attachments:
v19-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v19-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From 23f76c228f54c962a51d176d132207f0ad50e644 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v19 1/5] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1207 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1089 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3025 insertions(+), 73 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..d99ec29c6a 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..7b9f4b10a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..e7540c4756
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1207 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+ /* Set sha-256 as a default */
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+
+ /*
+ * Skip over the key= portion of the key=value containing the the peer
+ * CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3a78d2043e..eb1b028464 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bb34630e8e..a1a36cb5e2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..74f88b628e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e7781d010f..8188401659 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..787bc140a6
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1089 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the 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") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..ff3b7c2aa6 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..96d7f3f534 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -755,7 +773,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 17e480546c..be6df4758a 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1002,6 +1007,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v19-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v19-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From d6ac537d3d6e629d9ee5d9ec5ddc7534032195bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v19 2/5] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 8 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 611 insertions(+), 159 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 14cde4f5ba..1b6876ca18 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..21a654b8a9 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..7593d45877 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..7ed7c3b5ec 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,12 +11,16 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'channel binding not supported with NSS';
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v19-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v19-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 2a534c2fe3ed673132fd8577c5c5e84583135924 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v19 3/5] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 40 +++++++++++++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index d99ec29c6a..c15f8953ee 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 7b9f4b10a3..01b0ff98b3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 6d85f50b7c..99c1d17e9a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,21 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -114,6 +129,8 @@ pg_strong_random_init(void)
#elif defined(USE_DEV_URANDOM)
/* no initialization needed for /dev/urandom */
+#elif defined(USE_NSS_RANDOM)
+ /* TODO: set up NSS context here */
#else
#error no source of random numbers configured
#endif
@@ -202,6 +219,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v19-0004-NSS-Documentation.patchapplication/octet-stream; name=v19-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From a7944b4896ad3461f8d866e514ba8c38e9b18690 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v19 4/5] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f043433e31..f7ae3b1be1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 73c6a6ea4d..e420ced087 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..daa99cea74 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v19-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v19-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From b0c1a7ad03c4868e4f324c5bc72c4600a7392efb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v19 5/5] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
On Nov 13, 2020, at 4:14 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 12 Nov 2020, at 23:12, Jacob Champion <pchampion@vmware.com> wrote:
I'm not completely sure why this is exposed so easily with an OpenSSL
server -- I'm guessing the implementation slices up its packets
differently on the wire, causing a read event before NSS is able to
decrypt a full record -- but it's worth noting that this case also shows
up during NSS-to-NSS psql connections, when handling notifications at
the end of every query. PQconsumeInput() reports a hard failure with the
current implementation, but its return value is ignored by
PrintNotifications(). Otherwise this probably would have showed up
earlier.Should there perhaps be an Assert there to catch those?
Hm. From the perspective of helping developers out, perhaps, but from
the standpoint of "don't crash when an endpoint outside our control does
something strange", I think that's a harder sell. Should the error be
bubbled all the way up instead? Or perhaps, if psql isn't supposed to
treat notification errors as "hard" failures, it should at least warn
the user that something is fishy?
(What's the best way to test this case? Are there lower-level tests for
the protocol/network layer somewhere that I'm missing?)Not AFAIK. Having been knee-deep now, do you have any ideas on how to
implement?
I think that testing these sorts of important edge cases needs a
friendly DSL -- something that doesn't want to make devs tear their hair
out while building tests. I've been playing a little bit with Scapy [1]https://scapy.net/
to understand more of the libpq v3 protocol; I'll see if that can be
adapted for pieces of the TLS handshake in a way that's easy to
maintain. If it can be, maybe that'd be a good starting example.
I've incorporated this patch as well as the previous patch for the assertion
failure on private callback data into the attached v19 patchset. I also did a
spellcheck and pgindent run on it for ease of review.
Commit 6be725e70 got rid of some psql error messaging that the tests
were keying off of, so there are a few new failures after a rebase onto
latest master.
I've attached a patch that gets the SCRAM tests a little further
(certificate hashing was caught in an infinite loop). I also added error
checks to those loops, along the lines of the existing OpenSSL
implementation: if a suitable digest can't be found, the user will see
an error like
psql: error: could not find digest for OID 'PKCS #1 SHA-256 With RSA Encryption'
It's a little verbose but I don't think this case should come up in
normal practice.
--Jacob
[1]: https://scapy.net/
Attachments:
nss-fix-hang-when-hashing-certificates.patchapplication/octet-stream; name=nss-fix-hang-when-hashing-certificates.patchDownload
From 2f1d58f9c376429ca3ff84d0489824603d2bcd52 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 16 Nov 2020 08:55:33 -0800
Subject: [PATCH] nss: fix hang when hashing certificates
---
src/backend/libpq/be-secure-nss.c | 19 ++++++++++++-------
src/interfaces/libpq/fe-secure-nss.c | 22 ++++++++++++++++------
2 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
index e7540c4756..b1982ee596 100644
--- a/src/backend/libpq/be-secure-nss.c
+++ b/src/backend/libpq/be-secure-nss.c
@@ -813,6 +813,7 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
SECOidTag signature_alg;
SECOidTag digest_alg;
int digest_len;
+ const NSSSignatureAlgorithms *candidate;
SECStatus status;
PLArenaPool *arena = NULL;
SECItem digest;
@@ -824,20 +825,24 @@ be_tls_get_certificate_hash(Port *port, size_t *len)
return NULL;
signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
- /* Set sha-256 as a default */
- digest_alg = SEC_OID_SHA256;
- digest_len = SHA256_LENGTH;
- while (NSS_SCRAMDigestAlgorithm->signature)
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
{
- if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ if (signature_alg == candidate->signature)
{
- digest_alg = NSS_SCRAMDigestAlgorithm->hash;
- digest_len = NSS_SCRAMDigestAlgorithm->len;
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
break;
}
+
+ candidate++;
}
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
digest.len = digest_len;
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 787bc140a6..2f10e94f72 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -540,6 +540,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
SECOidTag signature_alg;
SECOidTag digest_alg;
int digest_len;
+ const NSSSignatureAlgorithms *candidate;
PLArenaPool *arena;
SECItem digest;
char *ret;
@@ -552,17 +553,26 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
return NULL;
signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
- digest_alg = SEC_OID_SHA256;
- digest_len = SHA256_LENGTH;
- while (NSS_SCRAMDigestAlgorithm->signature)
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
{
- if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ if (signature_alg == candidate->signature)
{
- digest_alg = NSS_SCRAMDigestAlgorithm->hash;
- digest_len = NSS_SCRAMDigestAlgorithm->len;
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
break;
}
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
}
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
--
2.24.1
On 16 Nov 2020, at 21:00, Jacob Champion <pchampion@vmware.com> wrote:
On Nov 13, 2020, at 4:14 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
I've incorporated this patch as well as the previous patch for the assertion
failure on private callback data into the attached v19 patchset. I also did a
spellcheck and pgindent run on it for ease of review.Commit 6be725e70 got rid of some psql error messaging that the tests
were keying off of, so there are a few new failures after a rebase onto
latest master.I've attached a patch that gets the SCRAM tests a little further
(certificate hashing was caught in an infinite loop). I also added error
checks to those loops, along the lines of the existing OpenSSL
implementation: if a suitable digest can't be found, the user will see
an error likepsql: error: could not find digest for OID 'PKCS #1 SHA-256 With RSA Encryption'
It's a little verbose but I don't think this case should come up in
normal practice.
Nice, thanks for the fix! I've incorporated your patch into the attached v20
which also fixes client side error reporting to be more readable. The SCRAM
tests are now also hooked up, albeit with SKIP blocks for NSS, so they can
start getting fixed.
cheers ./daniel
Attachments:
v20-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v20-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From af799b72951a9dca3823bbd496af86b026fa3978 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v20 1/5] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1211 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1105 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3045 insertions(+), 73 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..d99ec29c6a 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..7b9f4b10a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..54d4047cc2
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1211 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ 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);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+
+ /*
+ * Skip over the key= portion of the key=value containing the peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3a78d2043e..eb1b028464 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bb34630e8e..a1a36cb5e2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..74f88b628e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e7781d010f..8188401659 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..53ac06de27
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1105 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..ff3b7c2aa6 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..96d7f3f534 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -755,7 +773,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 17e480546c..be6df4758a 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1002,6 +1007,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v20-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v20-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 78c1cf721cce7bc2291d061e4af6dc9eccf58fd9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v20 2/5] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 177 +++++++++-
src/test/ssl/t/001_ssltests.pl | 328 ++++++++++--------
src/test/ssl/t/002_scram.pl | 97 ++++--
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
10 files changed, 664 insertions(+), 195 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index 14cde4f5ba..1b6876ca18 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index a6d2ffbf9e..6e6ca97d95 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -27,7 +27,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..21a654b8a9 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +63,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +94,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +125,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +208,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +236,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +276,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +321,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..5f75d4937d 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 98;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +398,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +423,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +439,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +515,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +540,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +558,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +584,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+else
+{
+ if ($ENV{with_ssl} eq 'openssl')
+ {
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+ }
+ elsif ($ENV{with_ssl} eq 'nss')
+ {
+ $nss = 1;
+ plan tests => 8;
+ }
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +81,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v20-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v20-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 493e16351ce5c796b367457b0e40efd1d19885ef Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v20 3/5] NSS pg_strong_random support
---
configure | 8 +++++++-
configure.ac | 5 ++++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 40 +++++++++++++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/configure b/configure
index d99ec29c6a..c15f8953ee 100755
--- a/configure
+++ b/configure
@@ -18426,6 +18426,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$USE_DEV_URANDOM" = x"1" ; then
$as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h
@@ -18435,7 +18441,7 @@ $as_echo "/dev/urandom" >&6; }
else
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/configure.ac b/configure.ac
index 7b9f4b10a3..01b0ff98b3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2215,13 +2215,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
+elif test x"$USE_NSS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation])
+ AC_MSG_RESULT([NSS])
elif test x"$USE_DEV_URANDOM" = x"1" ; then
AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation])
AC_MSG_RESULT([/dev/urandom])
else
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.])
fi
# If not set in template file, set bytes to use libc memset()
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 31f808398c..73c39b449c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -896,6 +896,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 6d85f50b7c..99c1d17e9a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -30,6 +30,21 @@
#ifdef USE_WIN32_RANDOM
#include <wincrypt.h>
#endif
+#ifdef USE_NSS_RANDOM
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+#endif
#ifdef USE_WIN32_RANDOM
/*
@@ -114,6 +129,8 @@ pg_strong_random_init(void)
#elif defined(USE_DEV_URANDOM)
/* no initialization needed for /dev/urandom */
+#elif defined(USE_NSS_RANDOM)
+ /* TODO: set up NSS context here */
#else
#error no source of random numbers configured
#endif
@@ -202,6 +219,29 @@ pg_strong_random(void *buf, size_t len)
}
return false;
+#elif defined(USE_NSS_RANDOM)
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+
/*
* Read /dev/urandom ourselves.
*/
--
2.21.1 (Apple Git-122.3)
v20-0004-NSS-Documentation.patchapplication/octet-stream; name=v20-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 3f0a4467f48afd730e971e4da2098dc5033a2188 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v20 4/5] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 +++++++++++++++++++
doc/src/sgml/config.sgml | 28 ++++++++++--
doc/src/sgml/installation.sgml | 30 ++++++++++++-
doc/src/sgml/libpq.sgml | 27 ++++++++++--
doc/src/sgml/runtime.sgml | 78 ++++++++++++++++++++++++++++++++--
5 files changed, 195 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..4f6f0cf353 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://sv.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a632cf98ba..1a6d4a36bf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1208,6 +1208,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1224,7 +1241,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1426,8 +1445,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 9d4b6ab4a8..bd570deb44 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7943,6 +7954,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 17e938148c..daa99cea74 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v20-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v20-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From fc7bbc41e50f8c0ff49bea998c5e53f421382071 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v20 5/5] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 61eabd2fc0..28b54d321b 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..f03e1f5fcf 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 29879b51af..379f861a56 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -442,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +479,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(
--
2.21.1 (Apple Git-122.3)
On Nov 17, 2020, at 7:00 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Nice, thanks for the fix! I've incorporated your patch into the attached v20
which also fixes client side error reporting to be more readable.
I was testing handshake failure modes and noticed that some FATAL
messages are being sent through to the client in cleartext. The OpenSSL
implementation doesn't do this, because it logs handshake problems at
COMMERROR level. Should we switch all those ereport() calls in the NSS
be_tls_open_server() to COMMERROR as well (and return explicitly), to
avoid this? Or was there a reason for logging at FATAL/ERROR level?
Related note, at the end of be_tls_open_server():
...
port->ssl_in_use = true;
return 0;error:
return 1;
}
This needs to return -1 in the error case; the only caller of
secure_open_server() does a direct `result == -1` comparison rather than
checking `result != 0`.
--Jacob
On Tue, 2020-10-27 at 21:07 +0100, Daniel Gustafsson wrote:
On 20 Oct 2020, at 21:15, Andres Freund <andres@anarazel.de> wrote:
+static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true;Hm. We either should have something similar to
/*
* Reject embedded NULLs in certificate common name to prevent
* attacks like CVE-2009-4034.
*/
if (len != strlen(peer_cn))
{
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("SSL certificate's common name contains embedded null")));
pfree(peer_cn);
return -1;
}
here, or a comment explaining why not.We should, but it's proving rather difficult as there is no equivalent API call
to get the string as well as the expected length of it.
I'm going to try to tackle this part next. It looks like NSS uses RFC
4514 (or something like it) backslash-quoting, which this code either
needs to undo or bypass before performing a comparison.
--Jacob
On Tue, Nov 17, 2020 at 04:00:53PM +0100, Daniel Gustafsson wrote:
Nice, thanks for the fix! I've incorporated your patch into the attached v20
which also fixes client side error reporting to be more readable. The SCRAM
tests are now also hooked up, albeit with SKIP blocks for NSS, so they can
start getting fixed.
On top of the set of TODO items mentioned in the logs of the patches,
this patch set needs a rebase because it does not apply. In order to
move on with this set, I would suggest to extract some parts of the
patch set independently of the others and have two buildfarm members
for the MSVC and non-MSVC cases to stress the parts that can be
committed. Just seeing the size, we could move on with:
- The ./configure set, with the change to introduce --with-ssl=openssl.
- 0004 for strong randoms.
- Support for cryptohashes.
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
This could be done separately.
src/sgml/libpq.sgml needs to document PQdefaultSSLKeyPassHook_nss, no?
--
Michael
On 18 Jan 2021, at 08:08, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Nov 17, 2020 at 04:00:53PM +0100, Daniel Gustafsson wrote:
Nice, thanks for the fix! I've incorporated your patch into the attached v20
which also fixes client side error reporting to be more readable. The SCRAM
tests are now also hooked up, albeit with SKIP blocks for NSS, so they can
start getting fixed.On top of the set of TODO items mentioned in the logs of the patches,
this patch set needs a rebase because it does not apply.
Fixed in the attached, which also addresses the points raised earlier by Jacob
as well as adds certificates created entirely by NSS tooling as well as initial
cryptohash support. There is something iffy with these certs (the test fails
on mismatching ciphers and/or signature algorithms) that I haven't been able to
pin down, but to get more eyes on this I'm posting the patch with the test
enabled. The NSS toolchain requires interactive input which makes the Makefile
a bit hacky, ideas on cleaning that up are appreciated.
In order to
move on with this set, I would suggest to extract some parts of the
patch set independently of the others and have two buildfarm members
for the MSVC and non-MSVC cases to stress the parts that can be
committed. Just seeing the size, we could move on with:
- The ./configure set, with the change to introduce --with-ssl=openssl.
- 0004 for strong randoms.
- Support for cryptohashes.
I will leave it to others to decide the feasibility of this, I'm happy to slice
and dice the commits into smaller bits to for example separate out the
--with-ssl autoconf change into a non NSS dependent commit, if that's wanted.
+/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE This could be done separately.
Based on an offlist discussion I believe this was a misunderstanding, but if I
instead misunderstood that feel free to correct me with how you think this
should be done.
src/sgml/libpq.sgml needs to document PQdefaultSSLKeyPassHook_nss, no?
Good point, fixed.
cheers ./daniel
Attachments:
v21-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v21-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From 2e44ad4a7709e700b5e651f238dffb2a439fcd58 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v21 1/6] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 422 +++++-
configure.ac | 56 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1265 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3122 insertions(+), 76 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index 8af4b99021..cc6088b545 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -710,7 +713,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -798,6 +800,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -855,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -867,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -935,6 +939,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1192,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1338,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1491,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1557,7 +1572,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1571,6 +1585,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8087,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12160,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12478,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13637,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13654,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14689,7 +15018,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14735,7 +15064,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14759,7 +15088,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14804,7 +15133,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14828,7 +15157,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -18106,6 +18435,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_openssl" = x"yes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_nss" = x"yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
diff --git a/configure.ac b/configure.ac
index 868a94c9ba..0da8cc9b0b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1406,11 +1435,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2165,6 +2199,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_openssl" = x"yes" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_nss" = x"yes" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..1b0c5ce1fa 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..111b46dea8
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1265 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+
+ /*
+ * Skip over the key= portion of the key=value containing the peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 371dccb852..12b105bbd1 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17579eeaca..0095a9980e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 93eb27a2aa..7a4b3dad5d 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -80,7 +80,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f4d9f3b408..67a9f90e5e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c4fde3f93d..837e105ffe 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2b78ed8ec3..79d3a35009 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 67b1e78512..0a1e47c5a2 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 5634b2d40c..a21eb088b3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -194,12 +194,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -257,12 +264,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 59a42bea97..d3d0ea3c95 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v21-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v21-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 28d9aa2c124cfffcfa3640e09aaba81ff5dc60dd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v21 2/6] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 235 +++++++++++-
src/test/ssl/t/001_ssltests.pl | 346 +++++++++++-------
src/test/ssl/t/002_scram.pl | 97 +++--
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++-
10 files changed, 749 insertions(+), 195 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index ab1ef9a475..cee4586eb5 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 59921b46cf..5391f461a2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,7 +28,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 93335b1ea2..e32b0f89d5 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +211,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +239,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +279,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +320,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..6b8bca2846 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+else
+{
+ if ($ENV{with_ssl} eq 'openssl')
+ {
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+ }
+ elsif ($ENV{with_ssl} eq 'nss')
+ {
+ $nss = 1;
+ plan tests => 8;
+ }
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +81,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v21-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v21-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From e296533d4e5c3491dc55856d475bf85a8c7183e2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v21 3/6] NSS pg_strong_random support
---
configure | 3 ++-
configure.ac | 3 ++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/configure b/configure
index cc6088b545..876a0ea839 100755
--- a/configure
+++ b/configure
@@ -18441,6 +18441,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
@@ -18467,7 +18468,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 0da8cc9b0b..6b6641a094 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2203,6 +2203,7 @@ elif test x"$with_nss" = x"yes" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2210,7 +2211,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 67a9f90e5e..0894ff2bef 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v21-0004-NSS-Documentation.patchapplication/octet-stream; name=v21-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From bb8a37864a562cb965083cde345bd3509fb24906 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v21 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 30 +++++++-
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 122 +++++++++++++++++++++++++++++++--
5 files changed, 310 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 82864bbb24..6738f8001f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1236,6 +1236,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1252,7 +1269,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1454,8 +1473,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2bb3bf77e4..3225736f56 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 283352d3a4..d51ba37219 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,87 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> --keyUsage keyEncipherment \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v21-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v21-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 3ced800109354ac497581fee8539a0267401b626 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v21 5/6] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index d881e85add..6f804bf032 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 3d74e15ec9..9eb8a3f74d 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a21eb088b3..0c489f9f34 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -444,9 +444,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -476,6 +481,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(
--
2.21.1 (Apple Git-122.3)
v21-0006-NSS-cryptohash-support.patchapplication/octet-stream; name=v21-0006-NSS-cryptohash-support.patch; x-unix-mode=0644Download
From 21f989bb9083f867446c0e964a13a6553adf6f75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v21 6/6] NSS cryptohash support
---
src/common/Makefile | 5 +
src/common/cryptohash_nss.c | 243 ++++++++++++++++++++++++++++++++++++
2 files changed, 248 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/Makefile b/src/common/Makefile
index 7a4b3dad5d..5a25e5b390 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,11 +85,16 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..9cd9c9f3f3
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,243 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ NSSInitContext *nss_context;
+ 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;
+
+#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);
+ ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx));
+#else
+ ctx = pg_malloc(sizeof(pg_cryptohash_ctx));
+#endif
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ ctx->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!ctx->nss_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return 1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ NSS_ShutdownContext(ctx->nss_context);
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+}
--
2.21.1 (Apple Git-122.3)
On 4 Dec 2020, at 01:57, Jacob Champion <pchampion@vmware.com> wrote:
On Nov 17, 2020, at 7:00 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Nice, thanks for the fix! I've incorporated your patch into the attached v20
which also fixes client side error reporting to be more readable.I was testing handshake failure modes and noticed that some FATAL
messages are being sent through to the client in cleartext. The OpenSSL
implementation doesn't do this, because it logs handshake problems at
COMMERROR level. Should we switch all those ereport() calls in the NSS
be_tls_open_server() to COMMERROR as well (and return explicitly), to
avoid this? Or was there a reason for logging at FATAL/ERROR level?
The ERROR logging made early development easier but then stuck around, I've
changed them to COMMERROR returning an error instead in the v21 patch just
sent to the list.
Related note, at the end of be_tls_open_server():
...
port->ssl_in_use = true;
return 0;error:
return 1;
}This needs to return -1 in the error case; the only caller of
secure_open_server() does a direct `result == -1` comparison rather than
checking `result != 0`.
Fixed.
cheers ./daniel
On Tue, 2021-01-19 at 21:21 +0100, Daniel Gustafsson wrote:
There is something iffy with these certs (the test fails
on mismatching ciphers and/or signature algorithms) that I haven't been able to
pin down, but to get more eyes on this I'm posting the patch with the test
enabled.
Removing `--keyUsage keyEncipherment` from the native_server-* CSR
generation seems to let the tests pass for me, but I'm wary of just
pushing that as a solution because I don't understand why that would
have anything to do with the failure mode
(SSL_ERROR_NO_SUPPORTED_SIGNATURE_ALGORITHM).
The NSS toolchain requires interactive input which makes the Makefile
a bit hacky, ideas on cleaning that up are appreciated.
Hm. I got nothing, short of a feature request to NSS...
--Jacob
On 20 Jan 2021, at 01:40, Jacob Champion <pchampion@vmware.com> wrote:
On Tue, 2021-01-19 at 21:21 +0100, Daniel Gustafsson wrote:
There is something iffy with these certs (the test fails
on mismatching ciphers and/or signature algorithms) that I haven't been able to
pin down, but to get more eyes on this I'm posting the patch with the test
enabled.Removing `--keyUsage keyEncipherment` from the native_server-* CSR
generation seems to let the tests pass for me, but I'm wary of just
pushing that as a solution because I don't understand why that would
have anything to do with the failure mode
(SSL_ERROR_NO_SUPPORTED_SIGNATURE_ALGORITHM).
Aha, that was a good clue, I had overlooked the required extensions in the CSR.
Re-reading RFC 5280 it seems we need keyEncipherment, dataEncipherment and
digitalSignature to create a valid SSL Server certificate. Adding those indeed
make the test pass. Skimming the certutil code *I think* removing it as you
did cause a set of defaults to kick in that made it work based on the parameter
"--nsCertType sslServer", but it's not entirely easy to make out. Either way,
relying on defaults in a test suite seems less than good, so I've extended the
Makefile to be explicit about the extensions.
The attached v22 rebase incorporates the fixup to the test Makefile, with not
further changes on top of that.
cheers ./daniel
Attachments:
v22-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v22-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From 3639ce7798695f32f9950a09b3a6a9a9b1ad90e3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v22 1/6] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 422 +++++-
configure.ac | 56 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1265 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3122 insertions(+), 76 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index 8af4b99021..cc6088b545 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -710,7 +713,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -798,6 +800,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -855,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -867,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -935,6 +939,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1192,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1338,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1491,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1557,7 +1572,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1571,6 +1585,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8087,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12160,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12478,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13637,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13654,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14689,7 +15018,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14735,7 +15064,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14759,7 +15088,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14804,7 +15133,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14828,7 +15157,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -18106,6 +18435,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_openssl" = x"yes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_nss" = x"yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
diff --git a/configure.ac b/configure.ac
index 868a94c9ba..0da8cc9b0b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1406,11 +1435,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2165,6 +2199,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_openssl" = x"yes" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_nss" = x"yes" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..1b0c5ce1fa 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..111b46dea8
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1265 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+
+ /*
+ * Skip over the key= portion of the key=value containing the peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 371dccb852..12b105bbd1 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17579eeaca..0095a9980e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 93eb27a2aa..7a4b3dad5d 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -80,7 +80,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f4d9f3b408..67a9f90e5e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c4fde3f93d..837e105ffe 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2b78ed8ec3..79d3a35009 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 67b1e78512..0a1e47c5a2 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 5634b2d40c..a21eb088b3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -194,12 +194,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -257,12 +264,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 59a42bea97..d3d0ea3c95 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v22-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v22-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 0c3bce7acbc845aca31251bcc5c564de39aa5f97 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v22 2/6] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 235 +++++++++++-
src/test/ssl/t/001_ssltests.pl | 346 +++++++++++-------
src/test/ssl/t/002_scram.pl | 97 +++--
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++-
10 files changed, 749 insertions(+), 195 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index ab1ef9a475..cee4586eb5 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 59921b46cf..5391f461a2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,7 +28,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 93335b1ea2..d275c89672 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +211,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +239,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +279,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +320,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..6b8bca2846 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+else
+{
+ if ($ENV{with_ssl} eq 'openssl')
+ {
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+ }
+ elsif ($ENV{with_ssl} eq 'nss')
+ {
+ $nss = 1;
+ plan tests => 8;
+ }
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +81,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v22-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v22-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From f8994dff2e9e49916a23e9fe66dfd0f9fd97841a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v22 3/6] NSS pg_strong_random support
---
configure | 3 ++-
configure.ac | 3 ++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/configure b/configure
index cc6088b545..876a0ea839 100755
--- a/configure
+++ b/configure
@@ -18441,6 +18441,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
@@ -18467,7 +18468,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 0da8cc9b0b..6b6641a094 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2203,6 +2203,7 @@ elif test x"$with_nss" = x"yes" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2210,7 +2211,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 67a9f90e5e..0894ff2bef 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v22-0004-NSS-Documentation.patchapplication/octet-stream; name=v22-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From c03bc0ffb8c0401a1f8c00d01ef5c3f395a8a7f0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v22 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 30 +++++++-
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 311 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 82864bbb24..6738f8001f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1236,6 +1236,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1252,7 +1269,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1454,8 +1473,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2bb3bf77e4..3225736f56 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 283352d3a4..0900ebf28f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v22-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v22-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 6ff1c2cc4b39883ac315ef654d4a58804653fda7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v22 5/6] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index d881e85add..6f804bf032 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 3d74e15ec9..9eb8a3f74d 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a21eb088b3..0c489f9f34 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -444,9 +444,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -476,6 +481,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(
--
2.21.1 (Apple Git-122.3)
v22-0006-NSS-cryptohash-support.patchapplication/octet-stream; name=v22-0006-NSS-cryptohash-support.patch; x-unix-mode=0644Download
From 52445591ec9154acf6497f1d787467bff10a2d13 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v22 6/6] NSS cryptohash support
---
src/common/Makefile | 5 +
src/common/cryptohash_nss.c | 243 ++++++++++++++++++++++++++++++++++++
2 files changed, 248 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/Makefile b/src/common/Makefile
index 7a4b3dad5d..5a25e5b390 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,11 +85,16 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..9cd9c9f3f3
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,243 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ NSSInitContext *nss_context;
+ 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;
+
+#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);
+ ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx));
+#else
+ ctx = pg_malloc(sizeof(pg_cryptohash_ctx));
+#endif
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ ctx->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!ctx->nss_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return 1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ NSS_ShutdownContext(ctx->nss_context);
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+}
--
2.21.1 (Apple Git-122.3)
On Wed, 2021-01-20 at 12:58 +0100, Daniel Gustafsson wrote:
Aha, that was a good clue, I had overlooked the required extensions in the CSR.
Re-reading RFC 5280 it seems we need keyEncipherment, dataEncipherment and
digitalSignature to create a valid SSL Server certificate. Adding those indeed
make the test pass. Skimming the certutil code *I think* removing it as you
did cause a set of defaults to kick in that made it work based on the parameter
"--nsCertType sslServer", but it's not entirely easy to make out.
Lovely. I didn't expect *removing* an extension to effectively *add*
more, but I'm glad it works now.
==
To continue the Subject Common Name discussion [1]/messages/by-id/7d6a23a7e30540b486abc823f7ced7a93e1da1e8.camel@vmware.com from a different
part of the thread:
Attached is a v23 version of the patchset that peels the raw Common
Name out from a client cert's Subject. This allows the following cases
that the OpenSSL implementation currently handles:
- subjects that don't begin with a CN
- subjects with quotable characters
- subjects that have no CN at all
Embedded NULLs are now handled in a similar manner to the OpenSSL side,
though because this failure happens during the certificate
authentication callback, it results in a TLS alert rather than simply
closing the connection.
For easier review of just the parts I've changed, I've also attached a
since-v22.diff, which is part of the 0001 patch.
--Jacob
[1]: /messages/by-id/7d6a23a7e30540b486abc823f7ced7a93e1da1e8.camel@vmware.com
/messages/by-id/7d6a23a7e30540b486abc823f7ced7a93e1da1e8.camel@vmware.com
Attachments:
since-v22.difftext/x-patch; name=since-v22.diffDownload
commit cf6a3e49488b9e795e28713f81f6a4d32702de5d
Author: Jacob Champion <pchampion@vmware.com>
Date: Tue Jan 19 10:32:27 2021 -0800
NSS: handle client cert Common Names like be-secure-openssl
Store an unquoted Common Name into port->peer_cn. This is the bulk of
the patch, because CERT_GetCommonName() always performs RFC 4514
quoting, and the authentication code in the core expects raw data for
direct string comparison.
These changes allow us to handle
- usernames with quotable characters, such as backslashes
- certificates with other name attributes besides Common Name (country
code, organizational unit, etc.)
- certificates without a Common Name
Certificates with embedded NULL bytes in the Common Name will be
rejected in the same manner as the OpenSSL implementation.
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
index 111b46dea8..80d7598662 100644
--- a/src/backend/libpq/be-secure-nss.c
+++ b/src/backend/libpq/be-secure-nss.c
@@ -1051,6 +1051,69 @@ pg_bad_cert_handler(void *arg, PRFileDesc * fd)
return SECFailure;
}
+/*
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len parameter.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN. Decode and copy it into a newly allocated buffer. */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code runs
+ * after certificate authentication has otherwise succeeded,
+ * you'd need to convince a CA implementation to sign a
+ * corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
static SECStatus
pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
{
@@ -1058,28 +1121,59 @@ pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServe
Port *port = (Port *) arg;
CERTCertificate *cert;
char *peer_cn;
- int len;
+ unsigned int len;
status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
- if (status == SECSuccess)
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
{
- cert = SSL_PeerCertificate(port->pr_fd);
- len = strlen(cert->subjectName);
- peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
- /*
- * Skip over the key= portion of the key=value containing the peer CN.
- */
- if (strncmp(cert->subjectName, "CN=", 3) == 0)
- strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
- else
- strlcpy(peer_cn, cert->subjectName, len + 1);
- CERT_DestroyCertificate(cert);
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
- port->peer_cn = peer_cn;
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
}
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
return status;
}
v23-0001-NSS-Frontend-Backend-and-build-infrastructure.patchtext/x-patch; name=v23-0001-NSS-Frontend-Backend-and-build-infrastructure.patchDownload
From 29d6390db3dd8d418c2abda24c285f621f1a5917 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v23 1/6] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 422 ++++-
configure.ac | 56 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1359 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 ++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3216 insertions(+), 76 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index 8af4b99021..cc6088b545 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -710,7 +713,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -798,6 +800,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -855,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -867,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -935,6 +939,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1187,6 +1192,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1324,7 +1338,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1477,6 +1491,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1557,7 +1572,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1571,6 +1585,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8087,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12160,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12478,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13637,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13654,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -14689,7 +15018,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14735,7 +15064,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14759,7 +15088,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14804,7 +15133,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14828,7 +15157,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -18106,6 +18435,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_openssl" = x"yes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_nss" = x"yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
diff --git a/configure.ac b/configure.ac
index 868a94c9ba..0da8cc9b0b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1406,11 +1435,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2165,6 +2199,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_openssl" = x"yes" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_nss" = x"yes" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1cad311436..1b0c5ce1fa 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8923,7 +8923,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..80d7598662
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1359 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len parameter.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN. Decode and copy it into a newly allocated buffer. */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code runs
+ * after certificate authentication has otherwise succeeded,
+ * you'd need to convince a CA implementation to sign a
+ * corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 371dccb852..12b105bbd1 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17579eeaca..0095a9980e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 93eb27a2aa..7a4b3dad5d 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -80,7 +80,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f4d9f3b408..67a9f90e5e 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c4fde3f93d..837e105ffe 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2b78ed8ec3..79d3a35009 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 67b1e78512..0a1e47c5a2 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 5634b2d40c..a21eb088b3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -194,12 +194,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -257,12 +264,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 59a42bea97..d3d0ea3c95 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.25.1
v23-0002-NSS-Testharness-updates.patchtext/x-patch; name=v23-0002-NSS-Testharness-updates.patchDownload
From cf3460f1ed6b24162e08ae9a17cfe2ffdd4418e8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v23 2/6] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 235 +++++++++++-
src/test/ssl/t/001_ssltests.pl | 346 +++++++++++-------
src/test/ssl/t/002_scram.pl | 97 +++--
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++-
10 files changed, 749 insertions(+), 195 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/Makefile b/src/test/Makefile
index ab1ef9a475..cee4586eb5 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 59921b46cf..5391f461a2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,7 +28,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..a2bed5336c 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 93335b1ea2..d275c89672 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +211,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +239,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +279,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +320,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..6b8bca2846 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+else
+{
+ if ($ENV{with_ssl} eq 'openssl')
+ {
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+ }
+ elsif ($ENV{with_ssl} eq 'nss')
+ {
+ $nss = 1;
+ plan tests => 8;
+ }
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +81,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.25.1
v23-0003-NSS-pg_strong_random-support.patchtext/x-patch; name=v23-0003-NSS-pg_strong_random-support.patchDownload
From 7cb545431b5d392e986c81a6253e44b24fd8b8d1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v23 3/6] NSS pg_strong_random support
---
configure | 3 ++-
configure.ac | 3 ++-
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/configure b/configure
index cc6088b545..876a0ea839 100755
--- a/configure
+++ b/configure
@@ -18441,6 +18441,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
@@ -18467,7 +18468,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 0da8cc9b0b..6b6641a094 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2203,6 +2203,7 @@ elif test x"$with_nss" = x"yes" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
@@ -2210,7 +2211,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 67a9f90e5e..0894ff2bef 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.25.1
v23-0004-NSS-Documentation.patchtext/x-patch; name=v23-0004-NSS-Documentation.patchDownload
From 5d06d659a102948b5d69071749a3b463e354fb1f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v23 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 30 +++++++-
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 311 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 82864bbb24..6738f8001f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1236,6 +1236,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1252,7 +1269,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1454,8 +1473,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 0ac1cb9999..bd09124fb0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,31 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-nss</option>
+ <indexterm>
+ <primary>NSS</primary>
+ <secondary>NSPR</secondary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections using <productname>NSS</productname>. This requires the
+ <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ <para>
+ This option is incompatible with <literal>--with-openssl</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -982,6 +1007,9 @@ build-postgresql:
your <productname>OpenSSL</productname> installation is sufficient
before proceeding.
</para>
+ <para>
+ This option is incompatible with <literal>--with-nss</literal>.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2bb3bf77e4..3225736f56 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 283352d3a4..0900ebf28f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.25.1
v23-0005-NSS-contrib-modules.patchtext/x-patch; name=v23-0005-NSS-contrib-modules.patchDownload
From 7cd09cd97fab9db08c1cc4079f718545da163076 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v23 5/6] NSS contrib modules
---
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
8 files changed, 864 insertions(+), 15 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..c2232e5a4d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index d881e85add..6f804bf032 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 3d74e15ec9..9eb8a3f74d 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-openssl</literal> and <literal>--with-nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..d2270cbe7c 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a21eb088b3..0c489f9f34 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -444,9 +444,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -476,6 +481,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(
--
2.25.1
v23-0006-NSS-cryptohash-support.patchtext/x-patch; name=v23-0006-NSS-cryptohash-support.patchDownload
From 1436b8b74f25c7a167cf78e7ef038de02f911bd8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v23 6/6] NSS cryptohash support
---
src/common/Makefile | 5 +
src/common/cryptohash_nss.c | 243 ++++++++++++++++++++++++++++++++++++
2 files changed, 248 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/Makefile b/src/common/Makefile
index 7a4b3dad5d..5a25e5b390 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,11 +85,16 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..9cd9c9f3f3
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,243 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ NSSInitContext *nss_context;
+ 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;
+
+#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);
+ ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx));
+#else
+ ctx = pg_malloc(sizeof(pg_cryptohash_ctx));
+#endif
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ ctx->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!ctx->nss_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return 1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ NSS_ShutdownContext(ctx->nss_context);
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
+}
--
2.25.1
On Tue, Jan 19, 2021 at 09:21:41PM +0100, Daniel Gustafsson wrote:
In order to
move on with this set, I would suggest to extract some parts of the
patch set independently of the others and have two buildfarm members
for the MSVC and non-MSVC cases to stress the parts that can be
committed. Just seeing the size, we could move on with:
- The ./configure set, with the change to introduce --with-ssl=openssl.
- 0004 for strong randoms.
- Support for cryptohashes.I will leave it to others to decide the feasibility of this, I'm happy to slice
and dice the commits into smaller bits to for example separate out the
--with-ssl autoconf change into a non NSS dependent commit, if that's wanted.
IMO it makes sense to extract the independent pieces and build on top
of them. The bulk of the changes is likely going to have a bunch of
comments if reviewed deeply, so I think that we had better remove from
the stack the small-ish problems to ease the next moves. The
./configure part and replacement of with_openssl by with_ssl is mixed
in 0001 and 0002, which is actually confusing. And, FWIW, I would be
fine with applying a patch that introduces a --with-ssl with a
compatibility kept for --with-openssl. This is what 0001 is doing,
actually, similarly to the past switches for --with-uuid.
A point that has been mentioned offline by you, but not mentioned on
this list. The structure of the modules in src/test/ssl/ could be
refactored to help with an easier integration of more SSL libraries.
This makes sense taken independently.
Based on an offlist discussion I believe this was a misunderstanding, but if I
instead misunderstood that feel free to correct me with how you think this
should be done.
The point would be to rename BITS_PER_BYTE to PG_BITS_PER_BYTE in the
code and avoid conflicts. I am not completely sure if others would
agree here, but this would remove quite some ifdef/undef stuff from
the code dedicated to NSS.
src/sgml/libpq.sgml needs to document PQdefaultSSLKeyPassHook_nss, no?
Good point, fixed.
Please note that patch 0001 is failing to apply after the recent
commit b663a41. There are conflicts in postgres_fdw.out.
Also, what's the minimum version of NSS that would be supported? It
would be good to define an acceptable older version, to keep that
documented and to track that perhaps with some configure checks (?),
similarly to what is done for OpenSSL.
Patch 0006 has three trailing whitespaces (git diff --check
complains). Running the regression tests of pgcrypto, I think that
the SHA2 implementation is not completely right. Some SHA2 encoding
reports results from already-freed data. I have spotted a second
issue within scram_HMAC_init(), where pg_cryptohash_create() remains
stuck inside NSS_InitContext(), freezing the regression tests where
password hashed for SCRAM are created.
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+ ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx));
+#else
+ ctx = pg_malloc(sizeof(pg_cryptohash_ctx));
+#endif
cryptohash_nss.c cannot use pg_malloc() for frontend allocations. On
OOM, your patch would call exit() directly, even within libpq. But
shared library callers need to know about the OOM failure.
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ pfree(ctx);
For similar reasons, pfree should not be used for the frontend code in
cryptohash_nss.c. The fallback should be just a malloc/free set.
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return 1;
+ return 0;
This needs to return -1 on failure, not 1.
I really need to study more the choide of the options chosen for
NSS_InitContext()... But based on the docs I can read on the matter I
think that saving nsscontext in pg_cryptohash_ctx is right for each
cryptohash built.
src/tools/msvc/ is missing an update for cryptohash_nss.c.
--
Michael
On Mon, 2020-07-20 at 15:35 +0200, Daniel Gustafsson wrote:
With this, I have one failing test ("intermediate client certificate is
provided by client") which I've left failing since I believe the case should be
supported by NSS. The issue is most likely that I havent figured out the right
certinfo incantation to make it so (Mozilla hasn't strained themselves when
writing documentation for this toolchain, or any part of NSS for that matter).
I think we're missing a counterpart to this piece of the OpenSSL
implementation, in be_tls_init():
if (ssl_ca_file[0])
{
...
SSL_CTX_set_client_CA_list(context, root_cert_list);
}
I think the NSS equivalent to SSL_CTX_set_client_CA_list() is probably
SSL_SetTrustAnchors() (which isn't called out in the online NSS docs,
as far as I can see).
What I'm less sure of is how we want the NSS counterpart to ssl_ca_file
to behave. The OpenSSL implementation allows a list of CA names to be
sent. Should the NSS side take a list of CA cert nicknames? a list of
Subjects? something else?
mod_nss for httpd had a proposed feature [1]https://bugzilla.redhat.com/show_bug.cgi?id=719401 to do this that
unfortunately withered on the vine, and Google returns ~500 results for
"SSL_SetTrustAnchors", so I'm unaware of any prior art in the wild...
--Jacob
On Thu, 2021-01-21 at 14:21 +0900, Michael Paquier wrote:
Also, what's the minimum version of NSS that would be supported? It
would be good to define an acceptable older version, to keep that
documented and to track that perhaps with some configure checks (?),
similarly to what is done for OpenSSL.
Some version landmarks:
- 3.21 adds support for extended master secret, which according to [1]https://tools.ietf.org/html/rfc7677#section-4
is required for SCRAM channel binding to actually be secure.
- 3.26 is Debian Stretch.
- 3.28 is Ubuntu 16.04, and RHEL6 (I think).
- 3.35 is Ubuntu 18.04.
- 3.36 is RHEL7 (I think).
- 3.39 gets us final TLS 1.3 support.
- 3.42 is Debian Buster.
- 3.49 is Ubuntu 20.04.
(I'm having trouble finding online package information for RHEL variants, so I've pulled those versions from online support docs. If someone notices that those are wrong please speak up.)
So 3.39 would guarantee TLS1.3 but exclude a decent chunk of still-
supported Debian-alikes. Anything less than 3.21 seems actively unsafe
unless we disable SCRAM with those versions.
Any other important landmarks (whether feature- or distro-related) we
need to consider?
--Jacob
On Wed, Jan 20, 2021 at 05:07:08PM +0000, Jacob Champion wrote:
Lovely. I didn't expect *removing* an extension to effectively *add*
more, but I'm glad it works now.
My apologies for chiming in. I was looking at your patch set here,
and while reviewing the strong random and cryptohash parts I have
found a couple of mistakes in the ./configure part. I think that the
switch from --with-openssl to --with-ssl={openssl} could just be done
independently as a building piece of the rest, then the first portion
based on NSS could just add the minimum set in configure.ac.
Please note that the patch set has been using autoconf from Debian, or
something forked from upstream. There were also missing updates in
several parts of the code base, and a lack of docs for the new
switch. I have spent time checking that with --with-openssl to make
sure that the obsolete grammar is still compatible, --with-ssl=openssl
and also without it.
Thoughts?
--
Michael
Attachments:
0001-Introduce-with-ssl-openssl-in-configure-options.patchtext/x-diff; charset=us-asciiDownload
From b3e18564697bdb36a8eba4afe6434294676b528b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 27 Jan 2021 16:36:45 +0900
Subject: [PATCH] Introduce --with-ssl={openssl} in configure options
---
src/include/pg_config.h.in | 2 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/hba.c | 2 +-
src/common/Makefile | 2 +-
src/interfaces/libpq/Makefile | 2 +-
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 2 +-
src/test/ssl/Makefile | 2 +-
src/test/ssl/t/001_ssltests.pl | 2 +-
src/test/ssl/t/002_scram.pl | 2 +-
doc/src/sgml/installation.sgml | 5 +-
doc/src/sgml/pgcrypto.sgml | 2 +-
doc/src/sgml/sslinfo.sgml | 2 +-
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 4 +-
configure | 110 +++++++++++-------
configure.ac | 31 +++--
src/Makefile.global.in | 2 +-
src/tools/msvc/Solution.pm | 2 +-
src/tools/msvc/config_default.pl | 2 +-
22 files changed, 113 insertions(+), 73 deletions(-)
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f4d9f3b408..55cab4d2bf 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -899,7 +899,7 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
-/* Define to build with OpenSSL support. (--with-openssl) */
+/* Define to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
/* Define to 1 to build with PAM support. (--with-pam) */
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..8d1d16b0fc 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,7 +28,7 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
endif
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 371dccb852..20bf1461ce 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-ssl=openssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/common/Makefile b/src/common/Makefile
index 1a1d0d3406..5422579a6a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -80,7 +80,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c4fde3f93d..2b7ace52e4 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,7 +45,7 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += \
fe-secure-common.o \
fe-secure-openssl.o
diff --git a/src/test/Makefile b/src/test/Makefile
index ab1ef9a475..f7859c2fd5 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 59921b46cf..5391f461a2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,7 +28,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..d9838a5d5a 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,7 +7,7 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+unless ($ENV{with_ssl} eq 'openssl')
{
plan skip_all => 'SSL not supported by this build';
}
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 93335b1ea2..d545382eea 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..9cf4ee24cb 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -11,7 +11,7 @@ use lib $FindBin::RealBin;
use SSLServer;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
plan tests => 93;
}
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..2bdd638115 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,7 @@ use lib $FindBin::RealBin;
use SSLServer;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_ssl} ne 'openssl')
{
plan skip_all => 'SSL not supported by this build';
}
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index a53389b728..2d52068310 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -967,7 +967,7 @@ build-postgresql:
</varlistentry>
<varlistentry>
- <term><option>--with-openssl</option>
+ <term><option>--with-ssl=<replaceable>LIBRARY</replaceable></option>
<indexterm>
<primary>OpenSSL</primary>
<seealso>SSL</seealso>
@@ -980,7 +980,8 @@ build-postgresql:
package to be installed. <filename>configure</filename> will check
for the required header files and libraries to make sure that
your <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ before proceeding. The only <replaceable>LIBRARY</replaceable>
+ supported now is <option>openssl</option>.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 3d74e15ec9..b6bb23de0f 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1154,7 +1154,7 @@ gen_random_uuid() returns uuid
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ <literal>--with-ssl=openssl</literal>.
</para>
<para>
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..2a9c45a111 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,7 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with <literal>--with-ssl=openssl</literal>.
</para>
<sect2>
diff --git a/contrib/Makefile b/contrib/Makefile
index 7a4866e338..ae1fd64028 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -52,7 +52,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 316a26e58d..c0b4f1fcf6 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -10,8 +10,8 @@ OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
+CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/configure b/configure
index e202697bbf..092e03e8ed 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,7 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -709,7 +710,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -854,7 +854,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -866,6 +865,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1556,7 +1557,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1570,6 +1570,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8070,41 +8072,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12174,7 +12141,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There is currently one supported SSL/TLS libraries: OpenSSL.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12435,8 +12459,14 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13322,7 +13352,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -18098,7 +18128,7 @@ fi
# will be used.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking which random number source to use" >&5
$as_echo_n "checking which random number source to use... " >&6; }
-if test x"$with_openssl" = x"yes" ; then
+if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
diff --git a/configure.ac b/configure.ac
index a5ad072ee4..f7a2db2574 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1205,7 +1196,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There is currently one supported SSL/TLS libraries: OpenSSL.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1229,7 +1234,11 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify openssl])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1402,7 +1411,7 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
@@ -2159,7 +2168,7 @@ fi
# first choice, else the native platform sources (Windows API or /dev/urandom)
# will be used.
AC_MSG_CHECKING([which random number source to use])
-if test x"$with_openssl" = x"yes" ; then
+if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a1688c97c..74b3a6acd2 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 2f28de0355..1c0c92fcd2 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -1156,7 +1156,7 @@ sub GetFakeConfigure
$cfg .= ' --with-ldap' if ($self->{options}->{ldap});
$cfg .= ' --without-zlib' unless ($self->{options}->{zlib});
$cfg .= ' --with-extra-version' if ($self->{options}->{extraver});
- $cfg .= ' --with-openssl' if ($self->{options}->{openssl});
+ $cfg .= ' --with-ssl=openssl' if ($self->{options}->{openssl});
$cfg .= ' --with-uuid' if ($self->{options}->{uuid});
$cfg .= ' --with-libxml' if ($self->{options}->{xml});
$cfg .= ' --with-libxslt' if ($self->{options}->{xslt});
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..5395e211eb 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -16,7 +16,7 @@ our $config = {
tcl => undef, # --with-tcl=<path>
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
- openssl => undef, # --with-openssl=<path>
+ openssl => undef, # --with-ssl=openssl with <path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.30.0
On Wed, 2021-01-27 at 16:39 +0900, Michael Paquier wrote:
My apologies for chiming in. I was looking at your patch set here,
and while reviewing the strong random and cryptohash parts I have
found a couple of mistakes in the ./configure part. I think that the
switch from --with-openssl to --with-ssl={openssl} could just be done
independently as a building piece of the rest, then the first portion
based on NSS could just add the minimum set in configure.ac.Please note that the patch set has been using autoconf from Debian, or
something forked from upstream. There were also missing updates in
several parts of the code base, and a lack of docs for the new
switch. I have spent time checking that with --with-openssl to make
sure that the obsolete grammar is still compatible, --with-ssl=openssl
and also without it.Thoughts?
Seems good to me on Ubuntu; builds with both flavors.
From peering at the Windows side:
--- a/src/tools/msvc/config_default.pl +++ b/src/tools/msvc/config_default.pl @@ -16,7 +16,7 @@ our $config = { tcl => undef, # --with-tcl=<path> perl => undef, # --with-perl=<path> python => undef, # --with-python=<path> - openssl => undef, # --with-openssl=<path> + openssl => undef, # --with-ssl=openssl with <path> uuid => undef, # --with-uuid=<path> xml => undef, # --with-libxml=<path> xslt => undef, # --with-libxslt=<path>
So to check understanding: the `openssl` config variable is still alive
for MSVC builds; it just turns that into `--with-ssl=openssl` in the
fake CONFIGURE_ARGS?
<bikeshed color="lightblue">
Since SSL is an obsolete term, and the choice of OpenSSL vs NSS vs
[nothing] affects server operation (such as cryptohash) regardless of
whether or not connection-level TLS is actually used, what would you
all think about naming this option --with-crypto? I.e.
--with-crypto=openssl
--with-crypto=nss
</bikeshed>
--Jacob
On Wed, Jan 27, 2021 at 06:47:17PM +0000, Jacob Champion wrote:
So to check understanding: the `openssl` config variable is still alive
for MSVC builds; it just turns that into `--with-ssl=openssl` in the
fake CONFIGURE_ARGS?
Yeah, I think that keeping both variables separated in the MSVC
scripts is the most straight-forward option, as this passes down a
path. Once there is a value for nss, we'd need to properly issue an
error if both OpenSSL and NSS are specified.
Since SSL is an obsolete term, and the choice of OpenSSL vs NSS vs
[nothing] affects server operation (such as cryptohash) regardless of
whether or not connection-level TLS is actually used, what would you
all think about naming this option --with-crypto? I.e.--with-crypto=openssl
--with-crypto=nss
Looking around, curl has multiple switches for each lib with one named
--with-ssl for OpenSSL, but it needs to be able to use multiple
libraries at run time. I can spot that libssh2 uses what you are
proposing. It seems to me that --with-ssl is a bit more popular but
not by that much: wget, wayland, some apache stuff (it uses a path as
option value). Anyway, what you are suggesting sounds like a good in
the context of Postgres. Daniel?
--
Michael
On 28 Jan 2021, at 07:06, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Jan 27, 2021 at 06:47:17PM +0000, Jacob Champion wrote:
Since SSL is an obsolete term, and the choice of OpenSSL vs NSS vs
[nothing] affects server operation (such as cryptohash) regardless of
whether or not connection-level TLS is actually used, what would you
all think about naming this option --with-crypto? I.e.--with-crypto=openssl
--with-crypto=nssLooking around, curl has multiple switches for each lib with one named
--with-ssl for OpenSSL, but it needs to be able to use multiple
libraries at run time.
To be fair, if we started over in curl I would push back on --with-ssl meaning
OpenSSL but that ship has long since sailed.
I can spot that libssh2 uses what you are
proposing. It seems to me that --with-ssl is a bit more popular but
not by that much: wget, wayland, some apache stuff (it uses a path as
option value). Anyway, what you are suggesting sounds like a good in
the context of Postgres. Daniel?
SSL is admittedly an obsolete technical term, but it's one that enough people
have decided is interchangeable with TLS that it's not a hill worth dying on
IMHO. Since postgres won't allow for using libnss or OpenSSL for cryptohash
*without* compiling SSL/TLS support (used or not), I think --with-ssl=LIB is
more descriptive and less confusing.
--
Daniel Gustafsson https://vmware.com/
On Thu, 2021-01-21 at 20:16 +0000, Jacob Champion wrote:
I think we're missing a counterpart to this piece of the OpenSSL
implementation, in be_tls_init():
Never mind. Using SSL_SetTrustAnchor is something we could potentially
do if we wanted to further limit the CAs that are actually sent to the
client, but it shouldn't be necessary to get the tests to pass.
I now think that it's just a matter of making sure that the "server-cn-
only" DB has the root_ca.crt included, so that it can correctly
validate the client certificate. Incidentally I think this should also
fix the remaining failing SCRAM test. I'll try to get a patch out
tomorrow, if adding the root CA doesn't invalidate some other test
logic.
--Jacob
On Fri, Jan 29, 2021 at 12:20:21AM +0100, Daniel Gustafsson wrote:
SSL is admittedly an obsolete technical term, but it's one that enough people
have decided is interchangeable with TLS that it's not a hill worth dying on
IMHO. Since postgres won't allow for using libnss or OpenSSL for cryptohash
*without* compiling SSL/TLS support (used or not), I think --with-ssl=LIB is
more descriptive and less confusing.
Okay, let's use --with-ssl then for the new switch name. The previous
patch is backward-compatible, and will simplify the rest of the set,
so let's move on with it. Once this is done, my guess is that it
would be cleaner to have a new patch that includes only the
./configure and MSVC changes, and then the rest: test refactoring,
cryptohash, strong random and lastly TLS (we may want to cut this a
bit more though and perhaps have some restrictions depending on the
scope of options a first patch set could support).
I'll wait a bit first to see if there are any objections to this
change.
--
Michael
On 21 Jan 2021, at 06:21, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Jan 19, 2021 at 09:21:41PM +0100, Daniel Gustafsson wrote:
In order to
move on with this set, I would suggest to extract some parts of the
patch set independently of the others and have two buildfarm members
for the MSVC and non-MSVC cases to stress the parts that can be
committed. Just seeing the size, we could move on with:
- The ./configure set, with the change to introduce --with-ssl=openssl.
- 0004 for strong randoms.
- Support for cryptohashes.I will leave it to others to decide the feasibility of this, I'm happy to slice
and dice the commits into smaller bits to for example separate out the
--with-ssl autoconf change into a non NSS dependent commit, if that's wanted.IMO it makes sense to extract the independent pieces and build on top
of them. The bulk of the changes is likely going to have a bunch of
comments if reviewed deeply, so I think that we had better remove from
the stack the small-ish problems to ease the next moves. The
./configure part and replacement of with_openssl by with_ssl is mixed
in 0001 and 0002, which is actually confusing. And, FWIW, I would be
fine with applying a patch that introduces a --with-ssl with a
compatibility kept for --with-openssl. This is what 0001 is doing,
actually, similarly to the past switches for --with-uuid.
This has been discussed elsewhere in the thread, so let's continue that there.
The attached v23 does however split off --with-ssl for OpenSSL in 0001, adding
the nss option in 0002.
A point that has been mentioned offline by you, but not mentioned on
this list. The structure of the modules in src/test/ssl/ could be
refactored to help with an easier integration of more SSL libraries.
This makes sense taken independently.
This has been submitted in F513E66A-E693-4802-9F8A-A74C1D0E3D10@yesql.se.
Based on an offlist discussion I believe this was a misunderstanding, but if I
instead misunderstood that feel free to correct me with how you think this
should be done.The point would be to rename BITS_PER_BYTE to PG_BITS_PER_BYTE in the
code and avoid conflicts. I am not completely sure if others would
agree here, but this would remove quite some ifdef/undef stuff from
the code dedicated to NSS.
Aha, now I see what you mean, sorry for the confusion. That can certainly be
done (and done so outside of this patchset), but it admittedly feels a bit
intrusive. If there is consensus that we should namespace our version like
this I'll go ahead and do that.
src/sgml/libpq.sgml needs to document PQdefaultSSLKeyPassHook_nss, no?
Good point, fixed.
Please note that patch 0001 is failing to apply after the recent
commit b663a41. There are conflicts in postgres_fdw.out.
Fixed.
Patch 0006 has three trailing whitespaces (git diff --check complains).
Fixed.
Running the regression tests of pgcrypto, I think that
the SHA2 implementation is not completely right. Some SHA2 encoding
reports results from already-freed data.
I've been unable to reproduce, can you shed some light on this?
I have spotted a second
issue within scram_HMAC_init(), where pg_cryptohash_create() remains
stuck inside NSS_InitContext(), freezing the regression tests where
password hashed for SCRAM are created.
I think the freezing you saw comes from opening and closing NSS contexts per
cryptohash op (some patience on my part runs the test Ok in ~30s which is
clearly not in the wheelhouse of acceptable), more on that below.
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner); + ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx)); +#else + ctx = pg_malloc(sizeof(pg_cryptohash_ctx)); +#endif cryptohash_nss.c cannot use pg_malloc() for frontend allocations. On OOM, your patch would call exit() directly, even within libpq. But shared library callers need to know about the OOM failure.
Of course, fixed.
+ status = PK11_DigestBegin(ctx->pk11_context); + + if (status != SECSuccess) + return 1; + return 0; This needs to return -1 on failure, not 1.
Doh, fixed.
I really need to study more the choide of the options chosen for
NSS_InitContext()... But based on the docs I can read on the matter I
think that saving nsscontext in pg_cryptohash_ctx is right for each
cryptohash built.
It's a safe but slow option, NSS wasn't really made for running a single crypto
operation. Since we are opening a context which isn't backed by an NSS
database we could have a static context, which indeed speeds up processing a
lot. The problem with that is that there is no good callsite for closing the
context as the backend is closing down. Since you are kneedeep in the
cryptohash code, do you have any thoughts on this? I've included 0008 which
implements this, with a commented out dummy stub for cleaning up.
Making nss_context static in cryptohash_nss.c is
appealing but there is no good option for closing it there. Any thoughts on
how to handle global contexts like this?
src/tools/msvc/ is missing an update for cryptohash_nss.c.
Fixed.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v23-0008-NSS-Make-the-cryptohash-NSSInitContext-static-as.patchapplication/octet-stream; name=v23-0008-NSS-Make-the-cryptohash-NSSInitContext-static-as.patch; x-unix-mode=0644Download
From 6d9b00b3b6d309d3374cfdb16bda3b95196285b4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 28 Jan 2021 22:44:46 +0100
Subject: [PATCH v23 8/8] NSS Make the cryptohash NSSInitContext static as an
experiment
We only need a static reference tot the NSS context, and opening one
per cryptohash operation is incredibly wasteful given that it wasn't
how NSS was designed to operate. This commit lacks the infrastructure
for closing the context on backend exit though, hence experimental.
---
src/common/cryptohash_nss.c | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
index d4fd2e0008..6b7e48a1b4 100644
--- a/src/common/cryptohash_nss.c
+++ b/src/common/cryptohash_nss.c
@@ -71,6 +71,8 @@
#define FREE(ptr) free(ptr)
#endif
+static NSSInitContext *nss_context = NULL;
+
/*
* Internal pg_cryptohash_ctx structure.
*/
@@ -78,7 +80,6 @@ struct pg_cryptohash_ctx
{
pg_cryptohash_type type;
- NSSInitContext *nss_context;
PK11Context *pk11_context;
HASH_HashType hash_type;
@@ -141,16 +142,20 @@ pg_cryptohash_create(pg_cryptohash_type type)
*/
memset(¶ms, 0, sizeof(params));
params.length = sizeof(params);
- ctx->nss_context = NSS_InitContext("", "", "", "", ¶ms,
- NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
- NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
- NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
- if (!ctx->nss_context)
+ if (!nss_context)
{
- explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
- FREE(ctx);
- goto error;
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
}
+
ctx->type = type;
hash = SECOID_FindOIDByTag(ctx->hash_type);
ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
@@ -252,7 +257,18 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
#ifndef FRONTEND
ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
#endif
- NSS_ShutdownContext(ctx->nss_context);
explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
FREE(ctx);
}
+
+/*
+ * TODO: We need a way to close the static NSS context when the backend is
+ * terminated.
+ *
+void
+pg_cryptohash_teardown(void)
+{
+ if (nss_context)
+ NSS_ShutdownContext(ctx->nss_context);
+}
+ */
--
2.21.1 (Apple Git-122.3)
v23-0007-NSS-cryptohash-support.patchapplication/octet-stream; name=v23-0007-NSS-cryptohash-support.patch; x-unix-mode=0644Download
From 313148be7d99d8ad7c0bf97abd9c6bee37169422 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v23 7/8] NSS cryptohash support
---
src/common/cryptohash_nss.c | 258 ++++++++++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +
2 files changed, 262 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..d4fd2e0008
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,258 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#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;
+
+ NSSInitContext *nss_context;
+ 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;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ ctx->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!ctx->nss_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ NSS_ShutdownContext(ctx->nss_context);
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 660387d0e8..180eb0aa3f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,10 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
--
2.21.1 (Apple Git-122.3)
v23-0006-NSS-contrib-modules.patchapplication/octet-stream; name=v23-0006-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 0d0a2152f1ff936af9ae9040388edc3484b6b757 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v23 6/8] NSS contrib modules
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
7 files changed, 863 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 3500bd4c72..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 3d74e15ec9..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 3213c039ca..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ff01acf3f6..660387d0e8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -445,9 +445,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -477,6 +482,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(
--
2.21.1 (Apple Git-122.3)
v23-0005-NSS-Documentation.patchapplication/octet-stream; name=v23-0005-NSS-Documentation.patch; x-unix-mode=0644Download
From c78e286bcab3bed4a5f5739981c1b0c4d69d44c3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v23 5/8] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 44 ++++++++++--
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 319 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f1037df5a9..00ad1553ae 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1236,6 +1236,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1252,7 +1269,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1454,8 +1473,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index a53389b728..bcfe6ad10c 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -250,7 +250,7 @@ su - postgres
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
+ You need a supported <acronym>SSL</acronym> library, if you want to support
encrypted client connections. <productname>OpenSSL</productname> is
also required for random number generation on platforms that do not
have <filename>/dev/urandom</filename> (except Windows). The minimum
@@ -966,6 +966,41 @@ build-postgresql:
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-ssl=<replaceable>LIBRARY</replaceable></option>
+ <indexterm>
+ <primary>SSL</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>SSL</acronym> (encrypted)
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-openssl</option>
<indexterm>
@@ -975,12 +1010,7 @@ build-postgresql:
</term>
<listitem>
<para>
- Build with support for <acronym>SSL</acronym> (encrypted)
- connections. This requires the <productname>OpenSSL</productname>
- package to be installed. <filename>configure</filename> will check
- for the required header files and libraries to make sure that
- your <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ Obsolete equivalent of <literal>--with-ssl=openssl</literal>.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0..89796de168 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..2578ee8c06 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v23-0004-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v23-0004-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From e985581373ed8f7e925c3721bff5d24b307124ac Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v23 4/8] NSS pg_strong_random support
---
configure | 1 +
configure.ac | 1 +
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/configure b/configure
index 143726c79c..2b83914289 100755
--- a/configure
+++ b/configure
@@ -18422,6 +18422,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
diff --git a/configure.ac b/configure.ac
index 59657a6010..6533e10ffd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2197,6 +2197,7 @@ elif test x"$with_ssl" = x"nss" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 8856a33590..fa5a977617 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-ssl=nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v23-0003-NSS-Testharness-updates.patchapplication/octet-stream; name=v23-0003-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 411ac43db0dff3c2e138afc8cb18828992000810 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v23 3/8] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/ssl/Makefile | 233 ++++++++++++
src/test/ssl/t/001_ssltests.pl | 346 +++++++++++-------
src/test/ssl/t/002_scram.pl | 97 +++--
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++-
6 files changed, 743 insertions(+), 189 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index d545382eea..d275c89672 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +211,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +239,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +279,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +320,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..6b8bca2846 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,17 +4,24 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_openssl} eq 'yes')
+if ($ENV{with_ssl} eq 'openssl')
{
+ $openssl = 1;
plan tests => 93;
}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
else
{
plan skip_all => 'SSL not supported by this build';
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..cf5714e544 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,24 +11,39 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_openssl} ne 'yes')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'no')
{
plan skip_all => 'SSL not supported by this build';
}
+else
+{
+ if ($ENV{with_ssl} eq 'openssl')
+ {
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+ }
+ elsif ($ENV{with_ssl} eq 'nss')
+ {
+ $nss = 1;
+ plan tests => 8;
+ }
+}
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +81,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +109,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v23-0002-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v23-0002-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From b266bc01d09ed8bb600c058a2d6a0e615f364c43 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v23 2/8] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 294 +++-
configure.ac | 31 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1265 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 5 +
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 5 +-
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 5 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
24 files changed, 3015 insertions(+), 23 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 07da84d401..59657a6010 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 07e06e5bf7..751ce081d7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8939,7 +8939,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/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 = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..111b46dea8
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1265 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc * fd);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+
+ /*
+ * Skip over the key= portion of the key=value containing the peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 371dccb852..9a04c093d5 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-ssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118e..b023b31c46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..be8b2583b2 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,17 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f4d9f3b408..8856a33590 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -899,12 +899,15 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
-/* Define to build with OpenSSL support. (--with-openssl) */
+/* Define to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-ssl=nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c5ef0bead5..837e105ffe 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -55,6 +55,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa9..1eeeff54a8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 00b87bdc96..44385ee391 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -421,6 +421,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -439,7 +442,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90328db04e..ff01acf3f6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -195,12 +195,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -258,12 +265,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 2f28de0355..aece3edfc8 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,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');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)
v23-0001-Introduce-with-ssl.patchapplication/octet-stream; name=v23-0001-Introduce-with-ssl.patch; x-unix-mode=0644Download
From b319d0be8ddbda4a4e0e3600521da83240bd6e73 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 27 Jan 2021 14:41:05 +0100
Subject: [PATCH v23 1/8] Introduce --with-ssl
---
configure | 110 +++++++++++-------
configure.ac | 31 +++--
contrib/Makefile | 2 +-
contrib/pgcrypto/Makefile | 4 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 2 +-
src/common/Makefile | 2 +-
src/interfaces/libpq/Makefile | 8 +-
src/test/Makefile | 2 +-
src/test/modules/Makefile | 2 +-
.../modules/ssl_passphrase_callback/Makefile | 2 +-
.../ssl_passphrase_callback/t/001_testfunc.pl | 4 +-
src/test/ssl/Makefile | 2 +-
13 files changed, 108 insertions(+), 65 deletions(-)
diff --git a/configure b/configure
index e202697bbf..ce9ea36999 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,7 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -709,7 +710,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -854,7 +854,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -866,6 +865,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1556,7 +1557,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1570,6 +1570,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8070,41 +8072,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12174,7 +12141,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There is currently only one supported SSL/TLS library: OpenSSL.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12435,8 +12459,14 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13322,7 +13352,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -18098,7 +18128,7 @@ fi
# will be used.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking which random number source to use" >&5
$as_echo_n "checking which random number source to use... " >&6; }
-if test x"$with_openssl" = x"yes" ; then
+if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
diff --git a/configure.ac b/configure.ac
index a5ad072ee4..07da84d401 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1205,7 +1196,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There is currently only one supported SSL/TLS library: OpenSSL.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1229,7 +1234,11 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify openssl])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1402,7 +1411,7 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
@@ -2159,7 +2168,7 @@ fi
# first choice, else the native platform sources (Windows API or /dev/urandom)
# will be used.
AC_MSG_CHECKING([which random number source to use])
-if test x"$with_openssl" = x"yes" ; then
+if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
diff --git a/contrib/Makefile b/contrib/Makefile
index cdc041c7db..ba414fe581 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -51,7 +51,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 316a26e58d..3500bd4c72 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -10,8 +10,8 @@ OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_SRCS), $(INT_SRCS))
+CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a1688c97c..74b3a6acd2 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..8d1d16b0fc 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,7 +28,7 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 1a1d0d3406..5422579a6a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -80,7 +80,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c4fde3f93d..c5ef0bead5 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,9 +45,13 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
diff --git a/src/test/Makefile b/src/test/Makefile
index ab1ef9a475..414749d627 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -28,7 +28,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 59921b46cf..5391f461a2 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -28,7 +28,7 @@ SUBDIRS = \
unsafe_tests \
worker_spi
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += ssl_passphrase_callback
else
ALWAYS_SUBDIRS += ssl_passphrase_callback
diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile
index f81265c296..a34d7ea46a 100644
--- a/src/test/modules/ssl_passphrase_callback/Makefile
+++ b/src/test/modules/ssl_passphrase_callback/Makefile
@@ -1,6 +1,6 @@
# ssl_passphrase_callback Makefile
-export with_openssl
+export with_ssl
MODULE_big = ssl_passphrase_func
OBJS = ssl_passphrase_func.o $(WIN32RES)
diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
index dbc084f870..c7bff75f07 100644
--- a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
+++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl
@@ -7,9 +7,9 @@ use TestLib;
use Test::More;
use PostgresNode;
-unless (($ENV{with_openssl} || 'no') eq 'yes')
+if ($ENV{with_ssl} eq 'no')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
my $clearpass = "FooBaR1";
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 93335b1ea2..d545382eea 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_ssl
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
--
2.21.1 (Apple Git-122.3)
On 29 Jan 2021, at 07:01, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Jan 29, 2021 at 12:20:21AM +0100, Daniel Gustafsson wrote:
SSL is admittedly an obsolete technical term, but it's one that enough people
have decided is interchangeable with TLS that it's not a hill worth dying on
IMHO. Since postgres won't allow for using libnss or OpenSSL for cryptohash
*without* compiling SSL/TLS support (used or not), I think --with-ssl=LIB is
more descriptive and less confusing.Okay, let's use --with-ssl then for the new switch name. The previous
patch is backward-compatible, and will simplify the rest of the set,
so let's move on with it. Once this is done, my guess is that it
would be cleaner to have a new patch that includes only the
./configure and MSVC changes, and then the rest: test refactoring,
cryptohash, strong random and lastly TLS (we may want to cut this a
bit more though and perhaps have some restrictions depending on the
scope of options a first patch set could support).I'll wait a bit first to see if there are any objections to this
change.
I'm still not convinced that adding --with-ssl=openssl is worth it before the
rest of NSS goes in (and more importantly, *if* it goes in).
On the one hand, we already have pluggable (for some value of) support for
adding TLS libraries, and adding --with-ssl is one more piece of that puzzle.
We could of course have endless --with-X options instead but as you say,
--with-uuid has set the tone here (and I believe that's good). On the other
hand, if we never add any other library than OpenSSL then it's just complexity
without benefit.
As mentioned elsewhere in the thread, the current v23 patchset has the
--with-ssl change as a separate commit to at least make it visual what it looks
like. The documentation changes are in the main NSS patch though since
documenting --with-ssl when there is only one possible value didn't seem to be
helpful to users whom are fully expected to use --with-openssl still.
--
Daniel Gustafsson https://vmware.com/
On Fri, 2021-01-29 at 13:57 +0100, Daniel Gustafsson wrote:
On 21 Jan 2021, at 06:21, Michael Paquier <michael@paquier.xyz> wrote:
I really need to study more the choide of the options chosen for
NSS_InitContext()... But based on the docs I can read on the matter I
think that saving nsscontext in pg_cryptohash_ctx is right for each
cryptohash built.It's a safe but slow option, NSS wasn't really made for running a single crypto
operation. Since we are opening a context which isn't backed by an NSS
database we could have a static context, which indeed speeds up processing a
lot. The problem with that is that there is no good callsite for closing the
context as the backend is closing down. Since you are kneedeep in the
cryptohash code, do you have any thoughts on this? I've included 0008 which
implements this, with a commented out dummy stub for cleaning up.Making nss_context static in cryptohash_nss.c is
appealing but there is no good option for closing it there. Any thoughts on
how to handle global contexts like this?
I'm completely new to this code, so take my thoughts with a grain of
salt...
I think the bad news is that the static approach will need support for
ENABLE_THREAD_SAFETY. (It looks like the NSS implementation of
pgtls_close() needs some thread support too?)
The good(?) news is that I don't understand why OpenSSL's
implementation of cryptohash doesn't _also_ need the thread-safety
code. (Shouldn't we need to call CRYPTO_set_locking_callback() et al
before using any of its cryptohash implementation?) So maybe we can
implement the same global setup/teardown API for OpenSSL too and not
have to one-off it for NSS...
--Jacob
On Fri, Jan 29, 2021 at 02:13:30PM +0100, Daniel Gustafsson wrote:
I'm still not convinced that adding --with-ssl=openssl is worth it before the
rest of NSS goes in (and more importantly, *if* it goes in).On the one hand, we already have pluggable (for some value of) support for
adding TLS libraries, and adding --with-ssl is one more piece of that puzzle.
We could of course have endless --with-X options instead but as you say,
--with-uuid has set the tone here (and I believe that's good). On the other
hand, if we never add any other library than OpenSSL then it's just complexity
without benefit.
IMO, one could say the same thing for any piece of refactoring we have
done in the past to make the TLS/crypto code more modular. There is
demand for being able to choose among multiple SSL libs at build time,
and we are still in a phase where we evaluate the options at hand.
This refactoring is just careful progress, and this is one step in
this direction. The piece about refactoring the SSL tests is
similar.
As mentioned elsewhere in the thread, the current v23 patchset has the
--with-ssl change as a separate commit to at least make it visual what it looks
like. The documentation changes are in the main NSS patch though since
documenting --with-ssl when there is only one possible value didn't seem to be
helpful to users whom are fully expected to use --with-openssl still.
The documentation changes should be part of the patch introducing the
switch IMO: a description of the new switch, as well as a paragraph
about the old value being deprecated. That's done this way for UUID.
--
Michael
On Fri, Jan 29, 2021 at 01:57:02PM +0100, Daniel Gustafsson wrote:
This has been discussed elsewhere in the thread, so let's continue that there.
The attached v23 does however split off --with-ssl for OpenSSL in 0001, adding
the nss option in 0002.
While going through 0001, I have found a couple of things.
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_SRCS), $(INT_SRCS))
+CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(OSSL_TESTS), $(INT_TESTS))
It seems to me that this part is the opposite, aka here the OpenSSL
files and tests (OSSL*) would be used if with_ssl is not openssl.
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
This split is better, good idea.
The two SSL tests still included a reference to with_openssl after
0001:
src/test/ssl/t/001_ssltests.pl:if ($ENV{with_openssl} eq 'yes')
src/test/ssl/t/002_scram.pl:if ($ENV{with_openssl} ne 'yes')
I have refreshed the docs on top to be consistent with the new
configuration, and applied it after more checks. I'll try to look in
more details at the failures with cryptohashes I found upthread.
--
Michael
On 20 Jan 2021, at 18:07, Jacob Champion <pchampion@vmware.com> wrote:
To continue the Subject Common Name discussion [1] from a different
part of the thread:Attached is a v23 version of the patchset that peels the raw Common
Name out from a client cert's Subject. This allows the following cases
that the OpenSSL implementation currently handles:- subjects that don't begin with a CN
- subjects with quotable characters
- subjects that have no CN at all
Nice, thanks for fixing this!
Embedded NULLs are now handled in a similar manner to the OpenSSL side,
though because this failure happens during the certificate
authentication callback, it results in a TLS alert rather than simply
closing the connection.
But returning SECFailure from the cert callback force NSS to terminate the
connection immediately doesn't it?
For easier review of just the parts I've changed, I've also attached a
since-v22.diff, which is part of the 0001 patch.
I confused my dev trees and missed to include this in the v23 that I sent out
(which should've been v24), sorry about that. Attached is a v24 which is
rebased on top of todays --with-ssl commit, and now includes your changes.
Additionally I've added a shutdown callback such that we close the connection
immediately if NSS is shutting down from underneath us. I can't imagine a
scenario in which that's benign, so let's take whatever precautions we can.
I've also changed the NSS initialization in the cryptohash code to closer match
what the NSS documentation recommends for similar scenarios, but more on that
downthread where that's discussed.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v24-0006-NSS-cryptohash-support.patchapplication/octet-stream; name=v24-0006-NSS-cryptohash-support.patch; x-unix-mode=0644Download
From 3155eba6b39fe583a764271359ef326e66cadd1a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v24 6/6] NSS cryptohash support
---
src/common/cryptohash_nss.c | 255 ++++++++++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +
2 files changed, 259 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..3e2b5776c1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,255 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 660387d0e8..180eb0aa3f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,10 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
--
2.21.1 (Apple Git-122.3)
v24-0005-NSS-contrib-modules.patchapplication/octet-stream; name=v24-0005-NSS-contrib-modules.patch; x-unix-mode=0644Download
From 216441c0910fa9728b9b458ec2fb7ab5645882bb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v24 5/6] NSS contrib modules
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
7 files changed, 863 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ff01acf3f6..660387d0e8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -445,9 +445,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -477,6 +482,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(
--
2.21.1 (Apple Git-122.3)
v24-0004-NSS-Documentation.patchapplication/octet-stream; name=v24-0004-NSS-Documentation.patch; x-unix-mode=0644Download
From 5eb0a47349f983709ee431b24dd6332b50b96bef Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v24 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 31 ++++++---
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 305 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e17cdcc816..b33cffb370 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1238,6 +1238,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1254,7 +1271,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1456,8 +1475,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..1b71881c6e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,15 +976,30 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
</varlistentry>
<varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0..89796de168 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..2578ee8c06 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v24-0003-NSS-pg_strong_random-support.patchapplication/octet-stream; name=v24-0003-NSS-pg_strong_random-support.patch; x-unix-mode=0644Download
From 60f57667edc89f2c95c3d99882c29d15b16903dd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v24 3/6] NSS pg_strong_random support
---
configure | 1 +
configure.ac | 1 +
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/configure b/configure
index 143726c79c..2b83914289 100755
--- a/configure
+++ b/configure
@@ -18422,6 +18422,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
diff --git a/configure.ac b/configure.ac
index 59657a6010..6533e10ffd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2197,6 +2197,7 @@ elif test x"$with_ssl" = x"nss" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 8856a33590..fa5a977617 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-ssl=nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v24-0002-NSS-Testharness-updates.patchapplication/octet-stream; name=v24-0002-NSS-Testharness-updates.patch; x-unix-mode=0644Download
From 6af78eaeed3301ff9586d32b0a3e0a64823b9340 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v24 2/6] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
---
src/test/ssl/Makefile | 233 ++++++++++++
src/test/ssl/t/001_ssltests.pl | 350 +++++++++++-------
src/test/ssl/t/002_scram.pl | 96 +++--
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++-
6 files changed, 743 insertions(+), 192 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index d545382eea..d275c89672 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +211,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +239,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +279,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +320,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7928de4e7c..6b8bca2846 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,20 +4,27 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 99;
}
else
{
- plan tests => 93;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,23 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-test_connect_ok(
+TODO:
+{
+ local $TODO = "WIP failure cause currently unknown";
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
+}
+
+test_connect_fails(
$common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..4d7874d420 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,29 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 8;
+}
+else
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -23,12 +41,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,20 +78,25 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-if ($supports_tls_server_end_point)
-{
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
-}
-else
+
+SKIP:
{
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
+ skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
+ if ($supports_tls_server_end_point)
+ {
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+ }
+ else
+ {
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
+ }
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -89,20 +106,21 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
-# because channel binding is not performed. Note that ssl/client.key may
-# be used in a different test, so the name of this temporary client key
-# is chosen here to be unique.
-my $client_tmp_key = "ssl/client_scram_tmp.key";
-copy("ssl/client.key", $client_tmp_key);
-chmod 0600, $client_tmp_key;
-test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
-
-# clean up
-unlink($client_tmp_key);
-
-done_testing($number_of_tests);
+SKIP:
+{
+ skip "NSS support not implemented yet", 1 if ($nss);
+ # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+ # because channel binding is not performed. Note that ssl/client.key may
+ # be used in a different test, so the name of this temporary client key
+ # is chosen here to be unique.
+ my $client_tmp_key = "ssl/client_scram_tmp.key";
+ copy("ssl/client.key", $client_tmp_key);
+ chmod 0600, $client_tmp_key;
+ test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+ # clean up
+ unlink($client_tmp_key);
+}
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v24-0001-NSS-Frontend-Backend-and-build-infrastructure.patchapplication/octet-stream; name=v24-0001-NSS-Frontend-Backend-and-build-infrastructure.patch; x-unix-mode=0644Download
From 121286d34fd65c6a3cdf6cc9c78ce4fbe87bf90d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v24 1/6] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 294 +++-
configure.ac | 31 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1402 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 5 +
src/include/common/pg_nss.h | 175 ++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 5 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
23 files changed, 3150 insertions(+), 22 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 07da84d401..59657a6010 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b09dce63f5..fbbe778217 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8928,7 +8928,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/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 = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..876e54be51
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1402 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 20bf1461ce..9a04c093d5 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-ssl=openssl to use SSL connections."),
+ errhint("Compile with --with-ssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118e..b023b31c46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..be8b2583b2 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,17 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 55cab4d2bf..8856a33590 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-ssl=nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa9..1eeeff54a8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 00b87bdc96..44385ee391 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -421,6 +421,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -439,7 +442,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90328db04e..ff01acf3f6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -195,12 +195,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -258,12 +265,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 1c0c92fcd2..5148708969 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
On 29 Jan 2021, at 19:46, Jacob Champion <pchampion@vmware.com> wrote:
I think the bad news is that the static approach will need support for
ENABLE_THREAD_SAFETY.
I did some more reading today and noticed that the NSS documentation (and their
sample code for doing crypto without TLS connections) says to use NSS_NoDB_Init
to perform a read-only init which don't require a matching close call. Now,
the docs aren't terribly clear and also seems to have gone offline from MDN,
and skimming the code isn't entirelt self-explanatory, so I may well have
missed something. The v24 patchset posted changes to this and at least passes
tests with decent performance so it seems worth investigating.
(It looks like the NSS implementation of pgtls_close() needs some thread
support too?)
Storing the context in conn would probably be better?
The good(?) news is that I don't understand why OpenSSL's
implementation of cryptohash doesn't _also_ need the thread-safety
code. (Shouldn't we need to call CRYPTO_set_locking_callback() et al
before using any of its cryptohash implementation?) So maybe we can
implement the same global setup/teardown API for OpenSSL too and not
have to one-off it for NSS...
No idea here, wouldn't that impact pgcrypto as well in that case?
--
Daniel Gustafsson https://vmware.com/
On 1 Feb 2021, at 14:25, Michael Paquier <michael@paquier.xyz> wrote:
I have refreshed the docs on top to be consistent with the new
configuration, and applied it after more checks.
Thanks, I was just about to send a rebased version earlier today with the doc
changes in the 0001 patch when this email landed in my inbox =) The v24 posted
upthread is now rebased on top of this.
I'll try to look in more details at the failures with cryptohashes I found
upthread.
Great, thanks.
--
Daniel Gustafsson https://vmware.com/
On Mon, 2021-02-01 at 21:49 +0100, Daniel Gustafsson wrote:
On 29 Jan 2021, at 19:46, Jacob Champion <pchampion@vmware.com> wrote:
I think the bad news is that the static approach will need support for
ENABLE_THREAD_SAFETY.I did some more reading today and noticed that the NSS documentation (and their
sample code for doing crypto without TLS connections) says to use NSS_NoDB_Init
to perform a read-only init which don't require a matching close call. Now,
the docs aren't terribly clear and also seems to have gone offline from MDN,
and skimming the code isn't entirelt self-explanatory, so I may well have
missed something. The v24 patchset posted changes to this and at least passes
tests with decent performance so it seems worth investigating.
Nice! Not having to close helps quite a bit.
(Looks like thread safety for NSS_Init was added in 3.13, so we have an
absolute version floor.)
(It looks like the NSS implementation of pgtls_close() needs some thread
support too?)Storing the context in conn would probably be better?
Agreed.
The good(?) news is that I don't understand why OpenSSL's
implementation of cryptohash doesn't _also_ need the thread-safety
code. (Shouldn't we need to call CRYPTO_set_locking_callback() et al
before using any of its cryptohash implementation?) So maybe we can
implement the same global setup/teardown API for OpenSSL too and not
have to one-off it for NSS...No idea here, wouldn't that impact pgcrypto as well in that case?
If pgcrypto is backend-only then I don't think it should need
multithreading protection; is that right?
--Jacob
On Mon, 2021-02-01 at 21:49 +0100, Daniel Gustafsson wrote:
Embedded NULLs are now handled in a similar manner to the OpenSSL side,
though because this failure happens during the certificate
authentication callback, it results in a TLS alert rather than simply
closing the connection.But returning SECFailure from the cert callback force NSS to terminate the
connection immediately doesn't it?
IIRC NSS will send the alert first, whereas our OpenSSL implementation
will complete the handshake and then drop the connection. I'll rebuild
with the latest and confirm.
For easier review of just the parts I've changed, I've also attached a
since-v22.diff, which is part of the 0001 patch.I confused my dev trees and missed to include this in the v23 that I sent out
(which should've been v24), sorry about that. Attached is a v24 which is
rebased on top of todays --with-ssl commit, and now includes your changes.
No problem. Thanks!
--Jacob
On Tue, Feb 02, 2021 at 12:42:23AM +0000, Jacob Champion wrote:
(Looks like thread safety for NSS_Init was added in 3.13, so we have an
absolute version floor.)
If that's the case, I would recommend to add at least something in the
section called install-requirements in the docs.
If pgcrypto is backend-only then I don't think it should need
multithreading protection; is that right?
No need for it in the backend, unless there are plans to switch from
processes to threads there :p
libpq, ecpg and anything using them have to care about that. Worth
noting that OpenSSL also has some special handling in libpq with
CRYPTO_get_id_callback() and that it tracks the number of opened
connections.
--
Michael
On Tue, 2021-02-02 at 00:55 +0000, Jacob Champion wrote:
On Mon, 2021-02-01 at 21:49 +0100, Daniel Gustafsson wrote:
Embedded NULLs are now handled in a similar manner to the OpenSSL side,
though because this failure happens during the certificate
authentication callback, it results in a TLS alert rather than simply
closing the connection.But returning SECFailure from the cert callback force NSS to terminate the
connection immediately doesn't it?IIRC NSS will send the alert first, whereas our OpenSSL implementation
will complete the handshake and then drop the connection. I'll rebuild
with the latest and confirm.
I wasn't able to reproduce the behavior I thought I saw before. In any
case I think the current NSS implementation for embedded NULLs will
work correctly.
Attached is a v24 which is
rebased on top of todays --with-ssl commit, and now includes your changes.
I have a v25 attached which fixes and re-enables the skipped/todo'd
client certificate and SCRAM tests. (Changes between v24 and v25 are in
since-v24.diff.) The server-cn-only database didn't have the root CA
installed to be able to verify client certificates, so I've added it.
Note that this changes the error message printed during the invalid-
root tests, because NSS is now sending the root of the chain. So the
server's issuer is considered untrusted rather than unrecognized.
--Jacob
Attachments:
since-v24.difftext/x-patch; name=since-v24.diffDownload
commit 093bb937736cdfc871d8fd479748f93f18108ae0
Author: Jacob Champion <pchampion@vmware.com>
Date: Fri Jan 29 09:32:27 2021 -0800
test/ssl: fix final TODOs and SKIPs
The server-cn-only database didn't have the root CA installed to be able
to verify client certificates. Note that this changes the error message
printed during the invalid-root tests, because the server is now sending
the root of the chain (so the issuer is untrusted rather than
unrecognized).
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index d275c89672..c8032a2c13 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -147,6 +147,7 @@ ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/se
certutil -d "sql:$@" -N --empty-password
certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 6b8bca2846..5a83a1d277 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -159,12 +159,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist|Peer's Certificate issuer is not recognized/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
@@ -604,14 +604,10 @@ switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
"user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
-TODO:
-{
- local $TODO = "WIP failure cause currently unknown";
- test_connect_ok(
- $common_connstr,
- "sslmode=require sslcert=ssl/client+client_ca.crt",
- "intermediate client certificate is provided by client");
-}
+test_connect_ok(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is provided by client");
test_connect_fails(
$common_connstr,
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 4d7874d420..43565bdf6e 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -29,7 +29,8 @@ if ($ENV{with_ssl} eq 'openssl')
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 8;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
}
else
{
@@ -79,24 +80,20 @@ test_connect_ok(
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
-SKIP:
+if ($supports_tls_server_end_point)
{
- skip "NSS support for TLS server endpoing is not implemented", 1 if ($nss);
- if ($supports_tls_server_end_point)
- {
- test_connect_ok(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- "SCRAM with SSL and channel_binding=require");
- }
- else
- {
- test_connect_fails(
- $common_connstr,
- "user=ssltestuser channel_binding=require",
- qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
- "SCRAM with SSL and channel_binding=require");
- }
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ "SCRAM with SSL and channel_binding=require");
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser channel_binding=require",
+ qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
+ "SCRAM with SSL and channel_binding=require");
}
# Now test when the user has an MD5-encrypted password; should fail
@@ -106,21 +103,17 @@ test_connect_fails(
qr/channel binding required but not supported by server's authentication request/,
"MD5 with SSL and channel_binding=require");
-SKIP:
-{
- skip "NSS support not implemented yet", 1 if ($nss);
- # Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
- # because channel binding is not performed. Note that ssl/client.key may
- # be used in a different test, so the name of this temporary client key
- # is chosen here to be unique.
- my $client_tmp_key = "ssl/client_scram_tmp.key";
- copy("ssl/client.key", $client_tmp_key);
- chmod 0600, $client_tmp_key;
- test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
- "dbname=certdb user=ssltestuser channel_binding=require",
- qr/channel binding required, but server authenticated client without channel binding/,
- "Cert authentication and channel_binding=require");
- # clean up
- unlink($client_tmp_key);
-}
+# Now test with auth method 'cert' by connecting to 'certdb'. Should fail,
+# because channel binding is not performed. Note that ssl/client.key may
+# be used in a different test, so the name of this temporary client key
+# is chosen here to be unique.
+my $client_tmp_key = "ssl/client_scram_tmp.key";
+copy("ssl/client.key", $client_tmp_key);
+chmod 0600, $client_tmp_key;
+test_connect_fails(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
+ "dbname=certdb user=ssltestuser channel_binding=require",
+ qr/channel binding required, but server authenticated client without channel binding/,
+ "Cert authentication and channel_binding=require");
+# clean up
+unlink($client_tmp_key);
v25-0001-NSS-Frontend-Backend-and-build-infrastructure.patchtext/x-patch; name=v25-0001-NSS-Frontend-Backend-and-build-infrastructure.patchDownload
From 0c00c0ba3a4197eddb7943c1098732384912d6cd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v25 1/6] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
Daniel Gustafsson, Andrew Dunstan, Jacob Champion
---
configure | 294 +++-
configure.ac | 31 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1402 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 5 +
src/include/common/pg_nss.h | 175 ++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 5 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 13 +-
src/interfaces/libpq/libpq-int.h | 20 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
23 files changed, 3150 insertions(+), 22 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 07da84d401..59657a6010 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b09dce63f5..fbbe778217 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8928,7 +8928,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/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 = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..876e54be51
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1402 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_alg));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 20bf1461ce..9a04c093d5 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-ssl=openssl to use SSL connections."),
+ errhint("Compile with --with-ssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118e..b023b31c46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..be8b2583b2 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,17 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,13 +193,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 55cab4d2bf..8856a33590 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-ssl=nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d27c8601fa..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa9..1eeeff54a8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..dca0572e67
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1112 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, 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/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ const NSSSignatureAlgorithms *candidate;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+ while (candidate->signature)
+ {
+ if (signature_alg == candidate->signature)
+ {
+ digest_alg = candidate->hash;
+ digest_len = candidate->len;
+ break;
+ }
+
+ candidate++;
+ }
+
+ if (!candidate->signature)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_alg));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 00b87bdc96..44385ee391 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -421,6 +421,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -439,7 +442,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..a95450fbba 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,23 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90328db04e..ff01acf3f6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -195,12 +195,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -258,12 +265,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 1c0c92fcd2..5148708969 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1005,6 +1010,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.25.1
v25-0002-NSS-Testharness-updates.patchtext/x-patch; name=v25-0002-NSS-Testharness-updates.patchDownload
From f64fd9a6a6f03d2fb6eb1d5c2c4f31bccf8eaf8f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:22:26 +0100
Subject: [PATCH v25 2/6] NSS Testharness updates
TODO:
* Generate certificate/keys with NSS tooling for NSS specific test
* Add test for certificate with embedded nulls
* Fix SCRAM tests when channel binding works
Daniel Gustafsson, Jacob Champion
---
src/test/ssl/Makefile | 234 ++++++++++++
src/test/ssl/t/001_ssltests.pl | 340 +++++++++++-------
src/test/ssl/t/002_scram.pl | 37 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 ++++-
6 files changed, 704 insertions(+), 163 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index d545382eea..c8032a2c13 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,79 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +212,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +240,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +280,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +321,73 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+#NSSFILES2 := ssl/nss/native_ca-root.db \
+# ssl/nss/native_server-root.db \
+# ssl/nss/native_client-root.db
+
+#nssfiles2: $(NSSFILES2)
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+nssfiles2clean:
+ rm -rf ssl/nss
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7928de4e7c..5a83a1d277 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,20 +4,27 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 99;
}
else
{
- plan tests => 93;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,36 +53,65 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them.
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -149,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -299,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -471,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +602,19 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..43565bdf6e 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,30 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -66,6 +79,7 @@ test_connect_ok(
$common_connstr,
"user=ssltestuser channel_binding=disable",
"SCRAM with SSL and channel_binding=disable");
+
if ($supports_tls_server_end_point)
{
test_connect_ok(
@@ -97,12 +111,9 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
-
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
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..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.25.1
v25-0003-NSS-pg_strong_random-support.patchtext/x-patch; name=v25-0003-NSS-pg_strong_random-support.patchDownload
From 61ebfcf1ab77a3e1b730601a8fa418353dc432ab Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:24:02 +0100
Subject: [PATCH v25 3/6] NSS pg_strong_random support
---
configure | 1 +
configure.ac | 1 +
src/include/pg_config.h.in | 3 +++
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
4 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/configure b/configure
index 143726c79c..2b83914289 100755
--- a/configure
+++ b/configure
@@ -18422,6 +18422,7 @@ $as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: /dev/urandom" >&5
$as_echo "/dev/urandom" >&6; }
diff --git a/configure.ac b/configure.ac
index 59657a6010..6533e10ffd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2197,6 +2197,7 @@ elif test x"$with_ssl" = x"nss" ; then
AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
+
else
AC_MSG_RESULT([/dev/urandom])
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 8856a33590..fa5a977617 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -908,6 +908,9 @@
/* Define to build with NSS support (--with-ssl=nss) */
#undef USE_NSS
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.25.1
v25-0004-NSS-Documentation.patchtext/x-patch; name=v25-0004-NSS-Documentation.patchDownload
From 6c23c72132419ed880793383e3efbf294ab4e218 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:26:31 +0100
Subject: [PATCH v25 4/6] NSS Documentation
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 31 ++++++---
doc/src/sgml/libpq.sgml | 99 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 305 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e17cdcc816..b33cffb370 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1238,6 +1238,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1254,7 +1271,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1456,8 +1475,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..1b71881c6e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,15 +976,30 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
</varlistentry>
<varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0..89796de168 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,72 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> is only available
+ when the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>NSS</productname>
+ support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> is only available
+ when the server was compiled with <productname>nss</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2569,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2594,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2628,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8065,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8096,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..2578ee8c06 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.25.1
v25-0005-NSS-contrib-modules.patchtext/x-patch; name=v25-0005-NSS-contrib-modules.patchDownload
From 0424241820e08e9a7f660f476083898a6976468a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:34:37 +0100
Subject: [PATCH v25 5/6] NSS contrib modules
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++
contrib/sslinfo/sslinfo.c | 32 ++
doc/src/sgml/pgcrypto.sgml | 38 +-
doc/src/sgml/sslinfo.sgml | 12 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 12 +-
7 files changed, 863 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..da66152d65
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,773 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2020, 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 "utils/memutils.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ff01acf3f6..660387d0e8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -445,9 +445,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -477,6 +482,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(
--
2.25.1
v25-0006-NSS-cryptohash-support.patchtext/x-patch; name=v25-0006-NSS-cryptohash-support.patchDownload
From 87f5997c5406f080b34ba5836ed197723bd0e28f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v25 6/6] NSS cryptohash support
---
src/common/cryptohash_nss.c | 255 ++++++++++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 4 +
2 files changed, 259 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..3e2b5776c1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,255 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2020, 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
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 660387d0e8..180eb0aa3f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,10 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
--
2.25.1
On Tue, Feb 02, 2021 at 08:33:35PM +0000, Jacob Champion wrote:
Note that this changes the error message printed during the invalid-
root tests, because NSS is now sending the root of the chain. So the
server's issuer is considered untrusted rather than unrecognized.
I think that it is not a good idea to attach the since-v*.diff patches
into the threads. This causes the CF bot to fail in applying those
patches.
Could it be possible to split 0001 into two parts at least with one
patch that includes the basic changes for the build and ./configure,
and a second with the FE/BE changes?
--
Michael
On Thu, 2021-02-04 at 16:30 +0900, Michael Paquier wrote:
On Tue, Feb 02, 2021 at 08:33:35PM +0000, Jacob Champion wrote:
Note that this changes the error message printed during the invalid-
root tests, because NSS is now sending the root of the chain. So the
server's issuer is considered untrusted rather than unrecognized.I think that it is not a good idea to attach the since-v*.diff patches
into the threads. This causes the CF bot to fail in applying those
patches.
Ah, sorry about that. Is there an extension I can use (or lack thereof)
that the CF bot will ignore, or does it scan the attachment contents?
--Jacob
On Thu, Feb 04, 2021 at 06:35:28PM +0000, Jacob Champion wrote:
Ah, sorry about that. Is there an extension I can use (or lack thereof)
that the CF bot will ignore, or does it scan the attachment contents?
The thing is smart, but there are ways to bypass it. Here is the
code:
https://github.com/macdice/cfbot/
And here are the patterns looked at:
cfbot_commitfest_rpc.py: groups = re.search('<a
href="(/message-id/attachment/[^"]*\\.(diff|diff\\.gz|patch|patch\\.gz|tar\\.gz|tgz|tar\\.bz2))">',
line)
--
Michael
On 4 Feb 2021, at 08:30, Michael Paquier <michael@paquier.xyz> wrote:
Could it be possible to split 0001 into two parts at least with one
patch that includes the basic changes for the build and ./configure,
and a second with the FE/BE changes?
Attached is a new patchset where I've tried to split the patches even further
to try and separate out changes for easier review. While not a perfect split
I'm sure, and clearly only for review purposes, I do hope it helps a little.
There is one hunk in 0002 which moves some OpenSSL specific code from
underneath USE_SSL, but thats about the only non-NSS change left in this
patchset AFAICS.
Additionally, this version moves the code in thee shared header to a proper .c
file shared between frontend and backend as well as performs some general
cleanup around that.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v26-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v26-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From d6e0f53f970461ec07e9616d0cac4db3e143fd48 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v26 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 6 +
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 20 +++
9 files changed, 396 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 07da84d401..59657a6010 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 55cab4d2bf..fa5a977617 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,12 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-ssl=nss) */
+#undef USE_NSS
+
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 49614106dc..53b5a71726 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -196,12 +201,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -259,12 +271,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -432,9 +451,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -464,6 +488,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 2aa062b2c9..0b0fbffa5e 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1008,6 +1013,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v26-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v26-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 0d933420191663ed58a81d2457d423c9160fe54e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v26 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 236 ++++++++++++++++++++++++++++++++++++
1 file changed, 236 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..140d562125
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,236 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+ SECStatus status;
+ unsigned int outlen;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(ctx->hash_type);
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v26-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v26-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 85e704dd47733aa8884f182f3b76639ca6a7fcd3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v26 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v26-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v26-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 544cde3f968341e3ff3ce7ddac6b07783260cfde Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v26 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 38 +-
3 files changed, 787 insertions(+), 11 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
--
2.21.1 (Apple Git-122.3)
v26-0006-nss-Documentation.patchapplication/octet-stream; name=v26-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 97da1c020bfd04448e065d87a12c876b57538638 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v26 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 43 ++++++++++++
doc/src/sgml/config.sgml | 28 +++++++-
doc/src/sgml/installation.sgml | 31 ++++++---
doc/src/sgml/libpq.sgml | 97 +++++++++++++++++++++++++-
doc/src/sgml/runtime.sgml | 123 +++++++++++++++++++++++++++++++--
5 files changed, 303 insertions(+), 19 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5ef1c7ad3c..5482d83e65 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1238,6 +1238,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1254,7 +1271,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1456,8 +1475,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..1b71881c6e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,15 +976,30 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
- </listitem>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
</varlistentry>
<varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0..ad2ec93848 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>nss</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -2497,6 +2567,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2592,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2626,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8063,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8094,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..2578ee8c06 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,88 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation" />
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</certutil> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v26-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v26-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 102b76c3d46ec19748ce4ce06ed9435972c30ccc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v26 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 50 ++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v26-0004-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v26-0004-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From a595749b283822921dd08e8eceeed33bcb6d9dcc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v26 04/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 225 +++++++++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 296 ++++++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 29 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++++++
4 files changed, 523 insertions(+), 100 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 d545382eea..6966dc23d2 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,79 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +212,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +240,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +280,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +321,64 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a6d79e2bfc..394505dd8b 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,7 +9,20 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -47,19 +60,58 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -107,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -195,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -220,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -233,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -243,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -257,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -270,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -286,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -298,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -323,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -364,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -429,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -453,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -468,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -494,14 +602,18 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..55fe3922e1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
--
2.21.1 (Apple Git-122.3)
v26-0003-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v26-0003-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 243948e1d5ebfd44e0a489813db673ccf6918269 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v26 03/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 62 ++---------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++++++++++---
4 files changed, 180 insertions(+), 71 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7928de4e7c..a6d79e2bfc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,20 +4,14 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
-}
-else
-{
- plan tests => 93;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -32,32 +26,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +40,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -546,7 +504,5 @@ test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
qr/SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v26-0002-nss-Remove-mentions-and-infra-of-OpenSSL-being-t.patchapplication/octet-stream; name=v26-0002-nss-Remove-mentions-and-infra-of-OpenSSL-being-t.patch; x-unix-mode=0644Download
From 8760fe28fff4842011c12fb0a29c22d0c3c11677 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:31 +0100
Subject: [PATCH v26 02/10] nss: Remove mentions and infra of OpenSSL being the
only TLS lib
This rewords comments and messages which indicate that --with-ssl only
has a single possible value, as well as moves OpenSSL specific code in
USE_SSL into USE_OPENSSL.
While in there, remove a trailing period from a single-line OpenSSL
comment to better match project style.
---
src/backend/libpq/hba.c | 2 +-
src/include/libpq/libpq-be.h | 5 +++--
src/include/pg_config_manual.h | 3 +--
src/interfaces/libpq/fe-secure.c | 5 ++++-
src/interfaces/libpq/libpq-fe.h | 2 +-
5 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 20bf1461ce..9a04c093d5 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-ssl=openssl to use SSL connections."),
+ errhint("Compile with --with-ssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index bc86fdfcdb..ea373a0ae0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,8 +193,9 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 0e50f61b94..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,8 +176,7 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 83ebd05ce5..3f726663f6 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -421,6 +421,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -439,7 +442,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
#ifndef USE_NSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 59ce0815cf..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -619,7 +619,7 @@ extern int pg_valid_server_encoding_id(int encoding);
/* === in fe-secure-openssl.c === */
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn);
extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
--
2.21.1 (Apple Git-122.3)
v26-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v26-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 7dbc26afad4d3e07c223e88102310fba27fc9519 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v26 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections. The implementation is done
as a drop-in replacement for the OpenSSL support and leverages the
same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1366 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1076 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 25 +-
15 files changed, 2786 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 60c7e115d6..3bec874fa6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..637d47e3c6
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1366 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (!pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4cf139a223..5e0c27c134 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118e..b023b31c46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..9799fef32d
--- /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 bse used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 66a8673d93..bc86fdfcdb 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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 d27c8601fa..0e50f61b94 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -179,7 +179,7 @@
* implementation. (Currently, only OpenSSL is supported, but we might add
* more implementations in the future.)
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa9..1eeeff54a8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..d2f8dc4902
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1076 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_database_has_CA
+ *
+ * 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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 00b87bdc96..83ebd05ce5 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -441,6 +441,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_SSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b13..59ce0815cf 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..c6f84096a0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,28 @@ struct pg_conn
* OpenSSL version changes */
#endif
#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
+ void *nss_context; /* NSS connection specific context */
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +782,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
On 4 Feb 2021, at 19:35, Jacob Champion <pchampion@vmware.com> wrote:
On Thu, 2021-02-04 at 16:30 +0900, Michael Paquier wrote:
On Tue, Feb 02, 2021 at 08:33:35PM +0000, Jacob Champion wrote:
Note that this changes the error message printed during the invalid-
root tests, because NSS is now sending the root of the chain. So the
server's issuer is considered untrusted rather than unrecognized.I think that it is not a good idea to attach the since-v*.diff patches
into the threads. This causes the CF bot to fail in applying those
patches.Ah, sorry about that. Is there an extension I can use (or lack thereof)
that the CF bot will ignore, or does it scan the attachment contents?
Naming the file .patch.txt should work, and it serves the double purpose of
making it extra clear that this is not a patch intended to be applied but one
intended to be read for informational purposes.
--
Daniel Gustafsson https://vmware.com/
On Tue, Feb 09, 2021 at 12:08:37AM +0100, Daniel Gustafsson wrote:
Attached is a new patchset where I've tried to split the patches even further
to try and separate out changes for easier review. While not a perfect split
I'm sure, and clearly only for review purposes, I do hope it helps a little.
There is one hunk in 0002 which moves some OpenSSL specific code from
underneath USE_SSL, but thats about the only non-NSS change left in this
patchset AFAICS.
I would have imagined 0010 to be either a 0001 or a 0002 :)
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
Indeed. Let's fix that on HEAD, as an independent thing.
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-ssl=openssl to use SSL connections."),
+ errhint("Compile with --with-ssl to use SSL connections."),
Actually, we could change that directly on HEAD as you suggest. This
code area is surrounded with USE_SSL so there is no need to mention
openssl at all.
-/* Support for overriding sslpassword handling with a callback. */
+/* Support for overriding sslpassword handling with a callback */
Makes sense.
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
Fine by me as well, meaning that 0002 could just be committed as-is.
I am also looking at 0003 a bit.
--
Michael
On 9 Feb 2021, at 07:47, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Feb 09, 2021 at 12:08:37AM +0100, Daniel Gustafsson wrote:
Attached is a new patchset where I've tried to split the patches even further
to try and separate out changes for easier review. While not a perfect split
I'm sure, and clearly only for review purposes, I do hope it helps a little.
There is one hunk in 0002 which moves some OpenSSL specific code from
underneath USE_SSL, but thats about the only non-NSS change left in this
patchset AFAICS.I would have imagined 0010 to be either a 0001 or a 0002 :)
Well, 0010 is a 2 in binary =) Jokes aside, I just didn't want to have a patch
referencing files added by later patches in the series.
errmsg("hostssl record cannot match because SSL is not supported by this build"), - errhint("Compile with --with-ssl=openssl to use SSL connections."), + errhint("Compile with --with-ssl to use SSL connections."), Actually, we could change that directly on HEAD as you suggest. This code area is surrounded with USE_SSL so there is no need to mention openssl at all.
We could, the only reason it says =openssl today is that it's the only possible
value but thats an implementation detail. Changing it now before it's shipped
anywhere means the translation will be stable even if another library is
supported.
0002 could just be committed as-is.
It can be, it's not the most pressing patch scope reduction but everything
helps of course.
I am also looking at 0003 a bit.
Thanks. That patch is slightly more interesting in terms of reducing scope
here, but I also think it makes the test code a bit easier to digest when
certificate management is abstracted into the API rather than the job of the
testfile to perform.
--
Daniel Gustafsson https://vmware.com/
On Tue, Feb 09, 2021 at 10:30:52AM +0100, Daniel Gustafsson wrote:
It can be, it's not the most pressing patch scope reduction but everything
helps of course.
Okay. I have spent some time on this one and finished it.
Thanks. That patch is slightly more interesting in terms of reducing scope
here, but I also think it makes the test code a bit easier to digest when
certificate management is abstracted into the API rather than the job of the
testfile to perform.
That's my impression. Still, I am wondering if there could be a
different approach. I need to think more about that first..
--
Michael
On 10 Feb 2021, at 08:23, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Feb 09, 2021 at 10:30:52AM +0100, Daniel Gustafsson wrote:
It can be, it's not the most pressing patch scope reduction but everything
helps of course.Okay. I have spent some time on this one and finished it.
Thanks, I'll post a rebased version on top of this soon.
Thanks. That patch is slightly more interesting in terms of reducing scope
here, but I also think it makes the test code a bit easier to digest when
certificate management is abstracted into the API rather than the job of the
testfile to perform.That's my impression. Still, I am wondering if there could be a
different approach. I need to think more about that first..
Another option could be to roll SSL config into PostgresNode and expose SSL
connections to every subsystem tested with TAP. Something like:
$node = get_new_node(..);
$node->setup_ssl(..);
$node->set_certificate(..);
That is a fair bit more work though, but perhaps we could then easier find
(and/or prevent) bugs like the one fixed in a45bc8a4f6495072bc48ad40a5aa03.
--
Daniel Gustafsson https://vmware.com/
On Mon, 2020-07-20 at 15:35 +0200, Daniel Gustafsson wrote:
This version adds support for sslinfo on NSS for most the functions.
I've poked around to see what can be done about the
unimplemented ssl_client_dn_field/ssl_issuer_field functions. There's a
nasty soup of specs to wade around in, and it's not really clear to me
which ones take precedence since they're mostly centered on LDAP.
My take on it is that OpenSSL has done its own thing here, with almost-
based-on-a-spec-but-not-quite semantics. NSS has no equivalents to many
of the field names that OpenSSL supports (e.g. "commonName"). Likewise,
OpenSSL doesn't support case-insensitivity (e.g. "cn" in addition to
"CN") as many of the relevant RFCs require. They do both support
dotted-decimal representations, so we could theoretically get feature
parity there without a huge amount of work.
For the few attributes that NSS has a public API for retrieving:
- common name
- country
- locality
- state
- organization
- domain component
- org. unit
- DN qualifier
- uid
- email address(es?)
we could hardcode the list of OpenSSL-compatible names, and just
translate manually in sslinfo. Then leave the rest up to dotted-decimal
OIDs.
Would that be desirable, or do we want this interface to be something
more generally compatible with (some as-of-yet unspecified) spec?
--Jacob
On 17 Feb 2021, at 02:02, Jacob Champion <pchampion@vmware.com> wrote:
On Mon, 2020-07-20 at 15:35 +0200, Daniel Gustafsson wrote:
This version adds support for sslinfo on NSS for most the functions.
I've poked around to see what can be done about the
unimplemented ssl_client_dn_field/ssl_issuer_field functions. There's a
nasty soup of specs to wade around in, and it's not really clear to me
which ones take precedence since they're mostly centered on LDAP.
Thanks for digging!
we could hardcode the list of OpenSSL-compatible names, and just
translate manually in sslinfo. Then leave the rest up to dotted-decimal
OIDs.Would that be desirable, or do we want this interface to be something
more generally compatible with (some as-of-yet unspecified) spec?
Regardless of approach taken I think this sounds like something that should be
tackled in a follow-up patch if the NSS patch is merged - and probably only as
a follow-up to a patch that adds test coverage to sslinfo. From the sounds of
things me may not be able to guarantee stability across OpenSSL versions as it
is right now?
--
Daniel Gustafsson https://vmware.com/
On 10 Feb 2021, at 13:17, Daniel Gustafsson <daniel@yesql.se> wrote:
On 10 Feb 2021, at 08:23, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Feb 09, 2021 at 10:30:52AM +0100, Daniel Gustafsson wrote:
It can be, it's not the most pressing patch scope reduction but everything
helps of course.Okay. I have spent some time on this one and finished it.
Thanks, I'll post a rebased version on top of this soon.
Attached is a rebase on top of this and the recent cryptohash changes to pass
in buffer lengths to the _final function. On top of that, I fixed up and
expanded the documentation, improved SCRAM handling (by using NSS digest
operations which are better suited) and reworded and expanded comments. This
patch version is, I think, feature complete with the OpenSSL implementation.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v27-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v27-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From c575be67573029f67783f1f4b8a6d62101ed0f53 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v27 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 6 +
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 20 +++
9 files changed, 396 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 07da84d401..59657a6010 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 55cab4d2bf..fa5a977617 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -905,6 +905,12 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-ssl=nss) */
+#undef USE_NSS
+
+/* Define to use NSS for random number generation */
+#undef USE_NSS_RANDOM
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 49614106dc..53b5a71726 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -196,12 +201,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -259,12 +271,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -432,9 +451,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -464,6 +488,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 2aa062b2c9..0b0fbffa5e 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1008,6 +1013,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v27-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v27-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From eaaffa978450d909deae19522b1dd6ba51baf5a2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v27 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v27-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v27-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 8d918c0ca525fba5d8739c669829d8e4b8634f04 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v27 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v27-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v27-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From d385ea06bf58817860b2dc770c6e452a9fca5beb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v27 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 38 +-
3 files changed, 787 insertions(+), 11 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
--
2.21.1 (Apple Git-122.3)
v27-0005-nss-Documentation.patchapplication/octet-stream; name=v27-0005-nss-Documentation.patch; x-unix-mode=0644Download
From d8e599419f1ed7f1bb7e0215fc579f821f14b2d6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v27 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 | 43 +++++
doc/src/sgml/config.sgml | 28 ++-
doc/src/sgml/installation.sgml | 31 +++-
doc/src/sgml/libpq.sgml | 305 ++++++++++++++++++++++++++-------
doc/src/sgml/runtime.sgml | 124 +++++++++++++-
5 files changed, 453 insertions(+), 78 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4df1405d2e..a260d42296 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1238,6 +1238,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1254,7 +1271,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1456,8 +1475,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0..d490134b64 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>nss</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1623,21 +1693,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ <para>
+ If security is not a primary concern, compression can improve
+ throughput if the network is the bottleneck. Disabling compression
+ can improve response time and throughput if CPU performance is the
+ limiting factor.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1646,10 +1726,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1658,15 +1752,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1677,28 +1786,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. If multiple different objects are password
+ protected, the same password is used for all.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1707,11 +1832,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1721,10 +1862,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. The default is
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. The default is
+ <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1760,8 +1918,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1776,8 +1934,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2497,6 +2655,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2520,9 +2680,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2549,6 +2714,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -7982,6 +8151,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8182,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..1fcad17a51 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v27-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v27-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 264af1bd9e574032c13d17edc9c46de16bcb3f8e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v27 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 | 50 ++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v27-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v27-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 4c5abf24bd4b357f6167179882ad401aae6614f8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v27 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 | 225 +++++++++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 296 ++++++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 29 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 73 ++++++++
4 files changed, 523 insertions(+), 100 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 d545382eea..6966dc23d2 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -30,6 +30,35 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -37,6 +66,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
#
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +97,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -77,6 +128,79 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +212,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -95,6 +240,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -127,19 +280,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -147,13 +321,64 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a6d79e2bfc..394505dd8b 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,7 +9,20 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 99;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -47,19 +60,58 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -107,82 +159,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -195,14 +270,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -220,12 +295,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -233,7 +308,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -243,12 +318,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
@@ -257,7 +332,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -270,14 +345,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -286,7 +361,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
@@ -298,11 +373,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL");
@@ -323,32 +398,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, 'native_server-root', 'native_ca-root');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, 'server-revoked');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -364,32 +457,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -429,18 +533,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -453,10 +558,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -468,7 +576,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -494,14 +602,18 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..55fe3922e1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..c5b31e584a
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,73 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname;
+
+ if ($certfile =~ /native/)
+ {
+ $cert_nickname = $certfile;
+ }
+ else
+ {
+ $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ }
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
--
2.21.1 (Apple Git-122.3)
v27-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v27-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From e080f3d1bfddca5487b33fc246d2114bf989921c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v27 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 62 ++---------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +++++++++++---
4 files changed, 180 insertions(+), 71 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7928de4e7c..a6d79e2bfc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,20 +4,14 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
-}
-else
-{
- plan tests => 93;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -32,32 +26,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +40,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -546,7 +504,5 @@ test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
qr/SSL error/, "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..ee901855b5 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -157,26 +184,51 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v27-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v27-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 4b9ebae85c0756f0092ae3ca15369246f1b662a5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v27 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. The implementation is done
as a drop-in replacement for the OpenSSL support and leverages the
same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1388 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1096 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 25 +-
15 files changed, 2828 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 60c7e115d6..3bec874fa6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..a17e6c7e5d
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1388 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (!pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 7155b33885..354d8e180b 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118e..b023b31c46 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..9799fef32d
--- /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 bse used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..eb00d9152b 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85a..d2860b852f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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 2a12071bad..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -178,7 +178,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa9..1eeeff54a8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..0548264f52
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1096 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon 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);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index c601071838..7f10da3010 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index effe0ccf85..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c..c6f84096a0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,28 @@ struct pg_conn
* OpenSSL version changes */
#endif
#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
+ void *nss_context; /* NSS connection specific context */
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -759,7 +782,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
On Wed, 2021-02-17 at 22:19 +0100, Daniel Gustafsson wrote:
On 17 Feb 2021, at 02:02, Jacob Champion <pchampion@vmware.com> wrote:
Would that be desirable, or do we want this interface to be something
more generally compatible with (some as-of-yet unspecified) spec?Regardless of approach taken I think this sounds like something that should be
tackled in a follow-up patch if the NSS patch is merged - and probably only as
a follow-up to a patch that adds test coverage to sslinfo.
Sounds good, and +1 to adding coverage at the same time.
From the sounds of
things me may not be able to guarantee stability across OpenSSL versions as it
is right now?
Yeah. I was going to write that OpenSSL would be unlikely to change
these once they're added for the first time, but after checking GitHub
it looks like they have done so recently [1]https://github.com/openssl/openssl/pull/10029, as part of a patch
release no less.
--Jacob
On Wed, 2021-02-17 at 22:35 +0100, Daniel Gustafsson wrote:
Attached is a rebase on top of this and the recent cryptohash changes to pass
in buffer lengths to the _final function. On top of that, I fixed up and
expanded the documentation, improved SCRAM handling (by using NSS digest
operations which are better suited) and reworded and expanded comments. This
patch version is, I think, feature complete with the OpenSSL implementation.
fe-secure-nss.c is no longer compiling as of this patchset; looks
like pgtls_open_client() has a truncated statement.
--Jacob
On 18 Feb 2021, at 21:33, Jacob Champion <pchampion@vmware.com> wrote:
On Wed, 2021-02-17 at 22:35 +0100, Daniel Gustafsson wrote:
Attached is a rebase on top of this and the recent cryptohash changes to pass
in buffer lengths to the _final function. On top of that, I fixed up and
expanded the documentation, improved SCRAM handling (by using NSS digest
operations which are better suited) and reworded and expanded comments. This
patch version is, I think, feature complete with the OpenSSL implementation.fe-secure-nss.c is no longer compiling as of this patchset; looks
like pgtls_open_client() has a truncated statement.
Ouch, I had a local mismerge that snuck in as I moved the branch around for
submission here. The attached fixes that as well as implements the sslcrldir
support that was committed recently. The crldir parameter isn't applicable to
NSS per se since all CRL's are loaded into the NSS database, but it does need
to be supported for the tests.
The crldir commit also made similar changes to the test harness as I had done
to support the NSS database, which made these incompatible. To fix that I've
implemented named parameters in switch_server_cert to make it less magic with
multiple optional parameters.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v28-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v28-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 35bf6312fb855d4dd18da3614d0c6b5881e4157d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v28 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 20 +++
9 files changed, 402 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index f54f65febe..5ec8970588 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 04dc330119..474cc2655c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -322,6 +322,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -334,6 +340,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -899,6 +908,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 49614106dc..53b5a71726 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -196,12 +201,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -259,12 +271,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -432,9 +451,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -464,6 +488,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 2aa062b2c9..0b0fbffa5e 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1008,6 +1013,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v28-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v28-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From d658748c3c752b31720a2203aff848ac83123b65 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v28 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v28-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v28-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 8c782dee692364d7ba03758b4c877452348e2b29 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v28 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v28-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v28-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 420eca69c0b97560cd29a514555448a3c1d429f5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v28 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 38 +-
3 files changed, 787 insertions(+), 11 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
--
2.21.1 (Apple Git-122.3)
v28-0005-nss-Documentation.patchapplication/octet-stream; name=v28-0005-nss-Documentation.patch; x-unix-mode=0644Download
From b225e9706348314cdf7f382e0dbf03c47e1a5c25 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v28 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 | 43 +++++
doc/src/sgml/config.sgml | 28 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 340 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 476 insertions(+), 90 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e81141e45c..7719f2914d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1305,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1509,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 5e25f20843..f84896470a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>nss</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1623,21 +1693,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ <para>
+ If security is not a primary concern, compression can improve
+ throughput if the network is the bottleneck. Disabling compression
+ can improve response time and throughput if CPU performance is the
+ limiting factor.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1646,10 +1726,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1658,15 +1752,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1677,28 +1786,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. If multiple different objects are password
+ protected, the same password is used for all.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1707,11 +1832,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1721,13 +1862,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1740,18 +1897,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1786,8 +1955,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1802,8 +1971,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2523,6 +2692,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2546,9 +2717,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2575,6 +2751,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8188,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8034,6 +8219,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..1fcad17a51 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v28-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v28-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 92a89e2af330650b7307ac85447e07dd1f809b45 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v28 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 | 50 ++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v28-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v28-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 42f6b684ca1ae89c7a6aab3da74b6eeab583c51d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v28 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 | 350 ++++++++++++++++++--------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 21 +-
src/test/ssl/t/SSL/Server.pm | 44 +---
6 files changed, 586 insertions(+), 165 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 4b53fdf6c0..8d4afa9dd0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -98,6 +262,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -130,19 +302,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -168,14 +361,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 30b68bddbe..dbd49817da 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 100;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 106;
}
else
{
- plan tests => 100;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,78 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 5
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +159,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -111,95 +179,118 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -212,14 +303,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,20 +328,20 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,21 +351,21 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -287,14 +378,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -303,11 +394,11 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -315,16 +406,16 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -345,32 +436,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -386,32 +495,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -451,18 +571,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -475,10 +596,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -490,7 +614,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -514,24 +638,28 @@ test_connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..801c945e88 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..ecceaefe57 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,18 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
- my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
- return $sslconf;
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
}
sub get_library
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index e59554f85b..da90b2c1d3 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -202,46 +201,21 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the server so that the configuration takes effect.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v28-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v28-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From da2520aec462b80609c318b6c196bcc24a61bf57 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v28 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 864f6e209f..30b68bddbe 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -574,7 +536,5 @@ test_connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..e59554f85b 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,14 +164,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -159,20 +186,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -182,13 +225,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v28-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v28-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 6d6179180f41746c65ab139e4cf0e85fc5fb84bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v28 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. The implementation is done
as a drop-in replacement for the OpenSSL support and leverages the
same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1388 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1088 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 25 +-
15 files changed, 2820 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..3d51a7c9b8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a..09ad2ad063 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..a17e6c7e5d
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1388 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (!pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d1545a2ad6..be583d7278 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 260ec7b97e..40b9c2a883 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4376,6 +4380,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4404,8 +4420,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..9799fef32d
--- /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 bse used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..eb00d9152b 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b41b10620a..ff67d0d20d 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -89,6 +89,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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 2a12071bad..7861b94290 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -178,7 +178,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db71fea169..40bc1cd348 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..72b0c54d1c
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1088 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database &&
+ strlen(conn->cert_database) > 0 &&
+ cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index c601071838..7f10da3010 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index effe0ccf85..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ce36aabd25..d7103f0bf2 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -363,6 +363,7 @@ struct pg_conn
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -486,6 +487,28 @@ struct pg_conn
* OpenSSL version changes */
#endif
#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
+ void *nss_context; /* NSS connection specific context */
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -760,7 +783,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
On Mon, 2021-02-22 at 14:31 +0100, Daniel Gustafsson wrote:
The attached fixes that as well as implements the sslcrldir
support that was committed recently. The crldir parameter isn't applicable to
NSS per se since all CRL's are loaded into the NSS database, but it does need
to be supported for the tests.The crldir commit also made similar changes to the test harness as I had done
to support the NSS database, which made these incompatible. To fix that I've
implemented named parameters in switch_server_cert to make it less magic with
multiple optional parameters.
The named parameters are a big improvement!
Couple things I've noticed with this patch, back on the OpenSSL side.
In SSL::Backend::OpenSSL's set_server_conf() implementation:
+ my $sslconf = + "ssl_ca_file='$params->{cafile}.crt'\n" + . "ssl_cert_file='$params->{certfile}.crt'\n" + . "ssl_key_file='$params->{keyfile}.key'\n" + . "ssl_crl_file='$params->{crlfile}'\n"; + $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir}; }
this is missing a `return $sslconf` at the end.
In 001_ssltests.pl:
-set_server_cert($node, 'server-cn-only', 'root+client_ca', - 'server-password', 'echo wrongpassword'); -command_fails( - [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], - 'restart fails with password-protected key file with wrong password'); -$node->_update_pid(0); +# Since the passphrase callbacks operate at different stages in OpenSSL and +# NSS we have two separate blocks for them +SKIP: +{ + skip "Certificate passphrases aren't checked on server restart in NSS", 2 + if ($nss); + + switch_server_cert($node, + certfile => 'server-cn-only', + cafile => 'root+client_ca', + keyfile => 'server-password', + nssdatabase => 'server-cn-only.crt__server-password.key.db', + passphrase_cmd => 'echo wrongpassword'); + + 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);
The removal of set_server_cert() in favor of switch_server_cert()
breaks these tests in OpenSSL, because the restart that
switch_server_cert performs will fail as designed. (The new comment
above switch_server_cert() suggests maybe you had a switch in mind to
skip the restart?)
NSS is not affected because we expect the restart to succeed:
+ command_ok( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart fails with password-protected key file with wrong password');
but I'd argue that that NSS test and the one after it should probably
be removed. We already know restart succeeded; otherwise
switch_server_cert() would have failed. (The test descriptions also
have the old "restart fails" verbiage.)
--Jacob
On 24 Feb 2021, at 01:11, Jacob Champion <pchampion@vmware.com> wrote:
On Mon, 2021-02-22 at 14:31 +0100, Daniel Gustafsson wrote:
The attached fixes that as well as implements the sslcrldir
support that was committed recently. The crldir parameter isn't applicable to
NSS per se since all CRL's are loaded into the NSS database, but it does need
to be supported for the tests.The crldir commit also made similar changes to the test harness as I had done
to support the NSS database, which made these incompatible. To fix that I've
implemented named parameters in switch_server_cert to make it less magic with
multiple optional parameters.The named parameters are a big improvement!
Couple things I've noticed with this patch, back on the OpenSSL side.
In SSL::Backend::OpenSSL's set_server_conf() implementation:+ my $sslconf = + "ssl_ca_file='$params->{cafile}.crt'\n" + . "ssl_cert_file='$params->{certfile}.crt'\n" + . "ssl_key_file='$params->{keyfile}.key'\n" + . "ssl_crl_file='$params->{crlfile}'\n"; + $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir}; }this is missing a `return $sslconf` at the end.
Yeah, I was clearly undercaffeinated and forgot to re-run the tests on OpenSSL
after some hackery. Fixed.
In 001_ssltests.pl:
-set_server_cert($node, 'server-cn-only', 'root+client_ca', - 'server-password', 'echo wrongpassword'); -command_fails( - [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], - 'restart fails with password-protected key file with wrong password'); -$node->_update_pid(0); +# Since the passphrase callbacks operate at different stages in OpenSSL and +# NSS we have two separate blocks for them +SKIP: +{ + skip "Certificate passphrases aren't checked on server restart in NSS", 2 + if ($nss); + + switch_server_cert($node, + certfile => 'server-cn-only', + cafile => 'root+client_ca', + keyfile => 'server-password', + nssdatabase => 'server-cn-only.crt__server-password.key.db', + passphrase_cmd => 'echo wrongpassword'); + + 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);The removal of set_server_cert() in favor of switch_server_cert()
breaks these tests in OpenSSL, because the restart that
switch_server_cert performs will fail as designed. (The new comment
above switch_server_cert() suggests maybe you had a switch in mind to
skip the restart?)
I initially had a restart => 'yes' parameter which turned too repetetive since
nearly all calls wants a restart. When I switched it to an opt-out I missed to
update the tests. Fixed.
NSS is not affected because we expect the restart to succeed:
+ command_ok( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart fails with password-protected key file with wrong password');but I'd argue that that NSS test and the one after it should probably
be removed. We already know restart succeeded; otherwise
switch_server_cert() would have failed. (The test descriptions also
have the old "restart fails" verbiage.)
Agreed, removed.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v29-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v29-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 373354960ac90aa18b5f20b44ffc6859e71abf03 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v29 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 20 +++
9 files changed, 402 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index f54f65febe..5ec8970588 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 04dc330119..474cc2655c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -322,6 +322,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -334,6 +340,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -899,6 +908,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 49614106dc..53b5a71726 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -196,12 +201,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -259,12 +271,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -432,9 +451,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -464,6 +488,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 2aa062b2c9..0b0fbffa5e 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -488,6 +488,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +541,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1008,6 +1013,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v29-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v29-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From e1772794d2f1f476d5124aab984f1015f3109bbc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v29 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v29-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v29-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 1e73a35c4d08aad1ee7f41172642aaa1e38581f0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v29 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v29-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v29-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 4bc85ff8cb3cdac35af2fe2ff48683e46c931228 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v29 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 38 +-
3 files changed, 787 insertions(+), 11 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
--
2.21.1 (Apple Git-122.3)
v29-0005-nss-Documentation.patchapplication/octet-stream; name=v29-0005-nss-Documentation.patch; x-unix-mode=0644Download
From d69dd599854fa85502ac9a36f97c4f42c7fb3623 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v29 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 | 43 +++++
doc/src/sgml/config.sgml | 28 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 340 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 476 insertions(+), 90 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d7a7376798..6eff5934e2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1305,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1509,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 5e25f20843..f84896470a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>nss</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1623,21 +1693,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ <para>
+ If security is not a primary concern, compression can improve
+ throughput if the network is the bottleneck. Disabling compression
+ can improve response time and throughput if CPU performance is the
+ limiting factor.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1646,10 +1726,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1658,15 +1752,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1677,28 +1786,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. If multiple different objects are password
+ protected, the same password is used for all.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1707,11 +1832,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1721,13 +1862,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1740,18 +1897,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1786,8 +1955,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1802,8 +1971,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2523,6 +2692,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2546,9 +2717,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2575,6 +2751,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8008,6 +8188,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8034,6 +8219,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..1fcad17a51 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v29-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v29-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From bd593ad86cf4d4fca3fe4a89c1b89832bedb9805 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v29 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 | 50 ++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v29-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v29-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 1ed0d6d2c20d8504090d8d166e6ca20491c108ea Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v29 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 | 342 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 582 insertions(+), 163 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 4b53fdf6c0..8d4afa9dd0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -98,6 +262,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -130,19 +302,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -168,14 +361,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 30b68bddbe..25efe791f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 103;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 104;
}
else
{
- plan tests => 100;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,70 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +151,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -111,95 +171,118 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -212,14 +295,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,20 +320,20 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,21 +343,21 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -287,14 +370,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -303,11 +386,11 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -315,16 +398,16 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -345,32 +428,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -386,32 +487,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -451,18 +563,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -475,10 +588,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -490,7 +606,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -514,24 +630,28 @@ test_connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..801c945e88 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index e59554f85b..da25645d25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -202,46 +201,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v29-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v29-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 8eeae109b4bbcc84b343128b20c371d450d66e51 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v29 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 864f6e209f..30b68bddbe 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -574,7 +536,5 @@ test_connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..e59554f85b 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,14 +164,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -159,20 +186,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -182,13 +225,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v29-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v29-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From ef8570b22e8d8360a28110d5bae4c11525e637f6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v29 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. The implementation is done
as a drop-in replacement for the OpenSSL support and leverages the
same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1388 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1088 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 25 +-
15 files changed, 2820 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..3d51a7c9b8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index baa0712c0f..db08a75326 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..a17e6c7e5d
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1388 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (!pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d1545a2ad6..be583d7278 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d626731723..8504a404df 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4376,6 +4380,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4404,8 +4420,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..9799fef32d
--- /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 bse used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..eb00d9152b 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b41b10620a..ff67d0d20d 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -89,6 +89,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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 f10ad0acd6..6e901beba1 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -178,7 +178,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db71fea169..40bc1cd348 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..72b0c54d1c
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1088 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database &&
+ strlen(conn->cert_database) > 0 &&
+ cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index c601071838..7f10da3010 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index effe0ccf85..aba32e6635 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ce36aabd25..d7103f0bf2 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -363,6 +363,7 @@ struct pg_conn
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -486,6 +487,28 @@ struct pg_conn
* OpenSSL version changes */
#endif
#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
+ void *nss_context; /* NSS connection specific context */
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -760,7 +783,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
Attached is a rebase which attempts to fix the cfbot Appveyor failure, there
were missing HAVE_ defines for MSVC.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v30-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v30-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From dddbaa5e7414cf05e7c5e7c26810d28f286e3285 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v30 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. The implementation is done
as a drop-in replacement for the OpenSSL support and leverages the
same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1388 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1088 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 25 +-
15 files changed, 2820 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..3d51a7c9b8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index baa0712c0f..db08a75326 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..a17e6c7e5d
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1388 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (!pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register out 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 = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+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 = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+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();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = 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 characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * 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, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index bb603ad209..43a5c5de42 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4bcf705a30..b6f980e35b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4309,7 +4309,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4377,6 +4381,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4405,8 +4421,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..9799fef32d
--- /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 bse used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7be1a67d69..eb00d9152b 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -283,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -293,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index e4e5c21565..ba31623a5b 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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 f10ad0acd6..6e901beba1 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -178,7 +178,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9812a14662..93124f8d42 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..72b0c54d1c
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1088 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, 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 we closed as we've applied the settings of the model
+ * onto the real socket. From hereon we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database &&
+ strlen(conn->cert_database) > 0 &&
+ cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index c601071838..7f10da3010 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 47a098b4b9..d9aa65d887 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -624,6 +624,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 0c9e95f1a7..f15af39222 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -383,6 +383,7 @@ struct pg_conn
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -507,6 +508,28 @@ struct pg_conn
* OpenSSL version changes */
#endif
#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
+ void *nss_context; /* NSS connection specific context */
+ void *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -781,7 +804,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
v30-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v30-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 4fae9756dfcfe37d86ca01d3c3879dd76b6f314f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v30 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 864f6e209f..30b68bddbe 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -574,7 +536,5 @@ test_connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..e59554f85b 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,14 +164,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -159,20 +186,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -182,13 +225,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v30-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v30-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 3f808ad0fa6c3022a473c14ff3e273223b1c799d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v30 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 | 342 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 582 insertions(+), 163 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 4b53fdf6c0..8d4afa9dd0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -98,6 +262,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -130,19 +302,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -168,14 +361,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 30b68bddbe..25efe791f3 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 103;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 104;
}
else
{
- plan tests => 100;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,70 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +151,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -111,95 +171,118 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -212,14 +295,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,20 +320,20 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,21 +343,21 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -287,14 +370,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -303,11 +386,11 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -315,16 +398,16 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -345,32 +428,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -386,32 +487,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -451,18 +563,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -475,10 +588,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -490,7 +606,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -514,24 +630,28 @@ test_connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..801c945e88 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index e59554f85b..da25645d25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -202,46 +201,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v30-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v30-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 1cf91c96694b75ee3270fef57ddc932577de48f6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v30 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 | 50 ++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..40de237628 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,7 +137,55 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
--
2.21.1 (Apple Git-122.3)
v30-0005-nss-Documentation.patchapplication/octet-stream; name=v30-0005-nss-Documentation.patch; x-unix-mode=0644Download
From 7fe27c0915af47f8d252c35ce3dde90cde4b4784 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v30 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 | 43 +++++
doc/src/sgml/config.sgml | 28 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 340 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 476 insertions(+), 90 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 967de73596..1608e9a7c7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,23 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for <acronym>SSL</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1305,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1509,11 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index cc20b0f234..73f94158c7 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -830,6 +830,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -846,6 +852,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>nss</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1638,21 +1708,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ <para>
+ If security is not a primary concern, compression can improve
+ throughput if the network is the bottleneck. Disabling compression
+ can improve response time and throughput if CPU performance is the
+ limiting factor.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1661,10 +1741,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1673,15 +1767,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1692,28 +1801,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. If multiple different objects are password
+ protected, the same password is used for all.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1722,11 +1847,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1736,13 +1877,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1755,18 +1912,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1801,8 +1970,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1817,8 +1986,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2597,6 +2766,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2620,9 +2791,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <literal>PRFileDesc</literal>.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2649,6 +2825,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8082,6 +8262,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8108,6 +8293,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..1fcad17a51 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which require converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v30-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v30-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 0874ca2ce4b0fb31f6c935c65fc1ca78747af0fd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v30 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 38 +-
3 files changed, 787 insertions(+), 11 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index b6bb23de0f..bf486f0c07 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
+ Any digest algorithm <productname>OpenSSL</productname> and
+ <productname>NSS</productname> supports
is automatically picked up.
This is not possible with ciphers, which need to be supported
explicitly.
--
2.21.1 (Apple Git-122.3)
v30-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v30-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 55fb70f3a3338d0c056ed682c1477a1ed7f1637b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v30 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v30-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v30-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 33148e547591226881d109aa6e4a71dda2279c62 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v30 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v30-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v30-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 96f38247b460a62e601d9d650d1336a46dc041c0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v30 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 408 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index ce9ea36999..143726c79c 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
@@ -1570,7 +1572,7 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12462,8 +12464,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13369,6 +13637,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18160,7 +18448,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index f54f65febe..5ec8970588 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1201,7 +1201,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1414,6 +1434,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2170,6 +2193,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2179,7 +2204,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 04dc330119..474cc2655c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -322,6 +322,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -334,6 +340,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -899,6 +908,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..e10d8bef42 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index ea3af48777..afe72a7e97 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 49614106dc..53b5a71726 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -132,6 +132,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -196,12 +201,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -259,12 +271,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -432,9 +451,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -464,6 +488,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 69b08591cc..f175d7aa24 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -299,10 +299,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLDAP_R => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -488,6 +491,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -540,6 +544,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -998,6 +1009,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
Attached is a rebase which attempts to fix the cfbot Appveyor failure, there
were missing HAVE_ defines for MSVC.
Subject: [PATCH v30 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. The implementation is done
maybe add 'using NSS' to that first sentence. ;)
+++ b/src/backend/libpq/auth.c @@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port) { int status_check_usermap = STATUS_ERROR;+#if defined(USE_OPENSSL) Assert(port->ssl); +#elif defined(USE_NSS) + /* TODO: should we rename pr_fd to ssl, to keep consistency? */ + Assert(port->pr_fd); +#else + Assert(false); +#endif
Having thought about this TODO item for a bit, I tend to think it's
better to keep them distinct. They aren't the same and it might not be
clear what's going on if one was to somehow mix them (at least if pr_fd
continues to sometimes be a void*, but I wonder why that's being
done..? more on that later..).
+++ b/src/backend/libpq/be-secure-nss.c
[...]
+/* default init hook can be overridden by a shared library */ +static void default_nss_tls_init(bool isServerStart); +nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+static PRDescIdentity pr_id; + +static PRIOMethods pr_iomethods;
Happy to be told I'm missing something, but the above two variables seem
to only be used in init_iolayer.. is there a reason they're declared
here instead of just being declared in that function?
+ /* + * 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. + */
Should we pull these error messages out into another header so that
they're in one place to make sure they're kept consistent, if we really
want to put the effort in to keep them the same..? I'm not 100% sure
that it's actually necessary to do so, but defining these in one place
would help maintain this if we want to. Also alright with just keeping
the comment, not that big of a deal.
+int +be_tls_open_server(Port *port) +{ + SECStatus status; + PRFileDesc *model; + PRFileDesc *pr_fd;
pr_fd here is materially different from port->pr_fd, no? As in, one is
the NSS raw TCP fd while the other is the SSL fd, right? Maybe we
should use two different variable names to try and make sure they don't
get confused? Might even set this to NULL after we are done with it
too.. Then again, I see later on that when we do the dance with the
'model' PRFileDesc that we just use the same variable- maybe we should
do that? That is, just get rid of this 'pr_fd' and use port->pr_fd
always?
+ /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. The documentation doesn't however clarify + * from which version this is holds true, so let's perform the potentially + * superfluous initialization anyways to avoid crashing on older versions + * of NSPR, as there is no difference in overhead. The NSS documentation + * still states that PR_Init must be called in some way (implicitly or + * explicitly). + * + * The below parameters are what the implicit initialization would've done + * for us, and should work even for older versions where it might not be + * done automatically. The last parameter, maxPTDs, is set to various + * values in other codebases, but has been unused since NSPR 2.1 which was + * released sometime in 1998. In current versions of NSPR all parameters + * are ignored. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ ); + + /* + * The certificate path (configdir) must contain a valid NSS database. If + * the certificate path isn't a valid directory, NSS will fall back on the + * system certificate database. If the certificate path is a directory but + * is empty then the initialization will fail. On the client side this can + * be allowed for any sslmode but the verify-xxx ones. + * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side + * we won't allow this to fail however, as we require the certificate and + * key to exist. + * + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456
The above seems to indicate that we will be requiring at least 3.12,
right? Yet above we have code to work with NSPR versions before 2.1?
Maybe we should put a stake in the ground that says "we only support
back to version X of NSS", test with that and a few more recent versions
and the most recent, and then rip out anything that's needed for
versions which are older than that? I have a pretty hard time imagining
that someone is going to want to build PG v14 w/ NSS 2.0 ...
+ { + char *ciphers, + *c; + + char *sep = ":;, "; + PRUint16 ciphercode; + const PRUint16 *nss_ciphers; + + /* + * If the user has specified a set of preferred cipher suites we start + * by turning off all the existing suites to avoid the risk of down- + * grades to a weaker cipher than expected. + */ + nss_ciphers = SSL_GetImplementedCiphers(); + for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++) + SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE); + + ciphers = pstrdup(SSLCipherSuites); + + for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep)) + { + if (!pg_find_cipher(c, &ciphercode)) + { + status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + return -1; + } + } + }
Maybe I'm a bit confused, but doesn't pg_find_cipher return *true* when
a cipher is found, and therefore the '!' above is saying "if we don't
find a matching cipher, then run the code to set the cipher ...". Also-
we don't seem to complain at all about a cipher being specified that we
don't find? Guess I would think that we might want to throw a WARNING
in such a case, but I could possibly be convinced otherwise. Kind of
wonder just what happens with the current code, I'm guessing ciphercode
is zero and therefore doesn't complain but also doesn't do what we want.
I wonder if there's a way to test this?
I do think we should probably throw an error if we end up with *no*
ciphers being set, which doesn't seem to be happening here..?
+ /* + * Set up the custom IO layer. + */
Might be good to mention that the IO Layer is what sets up the
read/write callbacks to be used.
+ port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + { + ereport(COMMERROR, + (errmsg("unable to initialize"))); + return -1; + }
Maybe a comment and a better error message for this?
+ PR_Close(model);
This might deserve one also, the whole 'model' construct is a bit
different. :)
+ port->ssl_in_use = true; + + /* Register out shutdown callback */
*our
+int +be_tls_get_cipher_bits(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.effectiveKeyBits; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return 0; +}
It doesn't have to be much, but I, at least, do prefer to see
function-header comments. :) Not that the OpenSSL code has them
consistently, so obviously not that big of a deal. Goes for a number of
the functions being added.
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
*decode
+static PRInt32 +pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRStatus +pg_ssl_close(PRFileDesc *fd) +{ + /* + * Disconnect our private Port from the fd before closing out the stack. + * (Debug builds of NSPR will assert if we do not.) + */ + fd->secret = NULL; + return PR_GetDefaultIOMethods()->close(fd); +}
Regarding these, I find myself wondering how they're different from the
defaults..? I mean, the above just directly called
PR_GetDefaultIOMethods() to then call it's close() function- are the
fd->lower_methods->recv/send not the default methods? I don't quite get
what the point is from having our own callbacks here if they just do
exactly what the defaults would do (or are there actually no defined
defaults and you have to provide these..?).
+/* + * 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, const char *guc_name)
guc_name isn't actually used in this function..? Is there some reason
to keep it or is it leftover?
Also, I get that they do similar jobs and that one is in the frontend
and the other is in the backend, but I'm not a fan of having two
'ssl_protocol_version_to_nss()'s functions that take different argument
types but have exact same name and do functionally different things..
+++ b/src/backend/utils/misc/guc.c @@ -4377,6 +4381,18 @@ static struct config_string ConfigureNamesString[] = check_canonical_path, assign_pgstat_temp_directory, NULL },+#ifdef USE_NSS + { + {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the NSS certificate database."), + NULL + }, + &ssl_database, + "", + NULL, NULL, NULL + }, +#endif
We don't #ifdef out the various GUCs even if SSL isn't compiled in, so
it doesn't seem quite right to be doing so here? Generally speaking,
GUCs that we expect people to use (rather than debugging ones and such)
are typically always built, even if we don't build support for that
capability, so we can throw a better error message than just some ugly
syntax or parsing error if we come across one being set in a non-enabled
build.
+++ 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 bse used if code is compiled with NSS support.
*be
+++ b/src/include/libpq/libpq-be.h @@ -200,6 +200,10 @@ typedef struct Port SSL *ssl; X509 *peer; #endif + +#ifdef USE_NSS + void *pr_fd; +#endif } Port;
Given this is under a #ifdef USE_NSS, does it need to be / should it
really be a void*?
+++ b/src/interfaces/libpq/fe-connect.c @@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */ offsetof(struct pg_conn, target_session_attrs)},+ {"cert_database", NULL, NULL, NULL, + "CertificateDatabase", "", 64, + offsetof(struct pg_conn, cert_database)},
I mean, maybe nitpicking here, but all the other SSL stuff is
'sslsomething' and the backend version of this is 'ssl_database', so
wouldn't it be more consistent to have this be 'ssldatabase'?
+++ b/src/interfaces/libpq/fe-secure-nss.c + * This logic exist in NSS as well, but it's only available for when there is
*exists
+ /* + * 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);
See same comment I made above- and also there's a comment earlier in
this file that we don't need to PR_Init() even ...
+ { + conn->nss_context = 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 create certificate database: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + }
That error message seems a bit ... off? Surely we aren't trying to
actually create a certificate database here?
+ /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure cipher policy: %s"), + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + }
Probably good to pull over at least some parts of the comments made in
the backend code about SetDomesticPolicy() actually enabling everything
(just like all the policies apparently do)...
+ /* + * 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->cert_database && conn->sslmode[0] == 'v') + { + status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\""); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), + ca_trust_name, + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + }
Maybe have something a bit more here about "maybe you should specifify a
cert_database" or such?
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0) + { + int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version); + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"), + conn->ssl_max_protocol_version); + return -1; + } + + desired_range.max = ssl_max_ver; + }
In the backend code, we have an additional check to make sure they
didn't set the min version higher than the max.. should we have that
here too? Either way, seems like we should be consistent.
+ * The model can now we closed as we've applied the settings of the model
*be
+ * onto the real socket. From hereon we should only use conn->pr_fd.
*here on
Similar comments to the backend code- should we just always use
conn->pr_fd? Or should we rename pr_fd to something else?
+ /* + * Specify which hostname we are expecting to talk to. This is required, + * albeit mostly applies to when opening a connection to a traditional + * http server it seems. + */ + SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
We should probably also set SNI, if available (NSS 3.12.6 it seems?),
since it looks like that's going to be added to the OpenSSL code.
+ do + { + status = SSL_ForceHandshake(conn->pr_fd); + } + while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
We don't seem to have this loop in the backend code.. Is there some
reason that we don't? Is it possible that we need to have a loop here
too? I recall in the GSS encryption code there were definitely things
during setup that had to be looped back over on both sides to make sure
everything was finished ...
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
Seems a bit grotty to do this (though I see that the OpenSSL code does
too ... at least there we have a comment though, maybe add one here?).
I would have thought we'd actually do strcmp()'s like above.
+ /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +}
Is there never a reason someone might want the pointer returned by
NSS_InitContext? I don't know that there is but it might be something
to consider (we could even possibly have our own structure returned by
this function which includes both, maybe..?). Not sure if there's a
sensible use-case for that or not just wanted to bring it up as it's
something I asked myself while reading through this patch.
+ if (strcmp(attribute_name, "protocol") == 0) + { + switch (channel.protocolVersion) + { +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + case SSL_LIBRARY_VERSION_TLS_1_3: + return "TLSv1.3"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + case SSL_LIBRARY_VERSION_TLS_1_2: + return "TLSv1.2"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + case SSL_LIBRARY_VERSION_TLS_1_1: + return "TLSv1.1"; +#endif + case SSL_LIBRARY_VERSION_TLS_1_0: + return "TLSv1.0"; + default: + return "unknown"; + } + }
Not sure that it really matters, but this seems like it might be useful
to have as its own function... Maybe even a data structure that both
functions use just in oppostie directions. Really minor tho. :)
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index c601071838..7f10da3010 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) } #endif /* USE_OPENSSL */+#ifndef USE_NSS + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return NULL; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + return; +} + +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + return NULL; +} +#endif /* USE_NSS */
Isn't this '!USE_NSS'?
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 0c9e95f1a7..f15af39222 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -383,6 +383,7 @@ struct pg_conn char *sslrootcert; /* root certificate filename */ char *sslcrl; /* certificate revocation list filename */ char *sslcrldir; /* certificate revocation list directory name */ + char *cert_database; /* NSS certificate/key database */ char *requirepeer; /* required peer credentials for local sockets */ char *gssencmode; /* GSS mode (require,prefer,disable) */ char *krbsrvname; /* Kerberos service name */ @@ -507,6 +508,28 @@ struct pg_conn * OpenSSL version changes */ #endif #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. + */
I'm a bit confused- what are the conflicts being caused here..?
Certainly under USE_OPENSSL we use the actual OpenSSL types..
Subject: [PATCH v30 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/<lib>
modules for the implementation details.No changes are done to the actual tests, this only change how setup and
teardown is performed.
Presumably this could be committed ahead of the main NSS support?
Subject: [PATCH v30 4/9] nss: pg_strong_random support +++ b/src/port/pg_strong_random.c +bool +pg_strong_random(void *buf, size_t len) +{ + NSSInitParameters params; + NSSInitContext *nss_context; + SECStatus status; + + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = 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 = PK11_GenerateRandom(buf, len); + NSS_ShutdownContext(nss_context); + + if (status == SECSuccess) + return true; + + return false; +} + +#else /* not USE_OPENSSL, USE_NSS or WIN32 */
I don't know that it's an issue, but do we actually need to init the NSS
context and shut it down every time..?
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
*or NSS
Subject: [PATCH v30 5/9] nss: Documentation +++ b/doc/src/sgml/acronyms.sgml @@ -684,6 +717,16 @@ </listitem> </varlistentry>+ <varlistentry> + <term><acronym>TLS</acronym></term> + <listitem> + <para> + <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security"> + Transport Layer Security</ulink> + </para> + </listitem> + </varlistentry>
We don't have this already..? Surely we should..
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 967de73596..1608e9a7c7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1272,6 +1272,23 @@ include_dir 'conf.d' </listitem> </varlistentry>+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database"> + <term><varname>ssl_database</varname> (<type>string</type>) + <indexterm> + <primary><varname>ssl_database</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Specifies the name of the file containing the server certificates and + keys when using <productname>NSS</productname> for <acronym>SSL</acronym> + connections. This parameter can only be set in the + <filename>postgresql.conf</filename> file or on the server command + line.
*SSL/TLS maybe?
@@ -1288,7 +1305,9 @@ include_dir 'conf.d' connections using TLS version 1.2 and lower are affected. There is currently no setting that controls the cipher choices used by TLS version 1.3 connections. The default value is - <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a + <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have + been built with <productname>OpenSSL</productname> as the + <acronym>SSL</acronym> library. The default is usually a reasonable choice unless you have specific security requirements. </para>
Shouldn't we say something here wrt NSS?
@@ -1490,8 +1509,11 @@ include_dir 'conf.d' <para> Sets an external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained. By - default, this parameter is empty, which means the built-in prompting - mechanism is used. + default, this parameter is empty. When the server is using + <productname>OpenSSL</productname>, this means the built-in prompting + mechanism is used. When using <productname>NSS</productname>, there is + no default prompting so a blank callback will be used returning an + empty password. </para>
Maybe we should point out here that this requires the database to not
require a password..? So if they have one, they need to set this, or
maybe we should provide a default one..
+++ b/doc/src/sgml/libpq.sgml +<synopsis> +PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void); +</synopsis> + </para> + + <para> + <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the + server was compiled with <productname>nss</productname> support. + </para>
We should try to be consistent- above should be NSS, not nss.
+ <listitem> + <para> + <productname>NSS</productname>: specifying the parameter is required + in case any password protected items are referenced in the + <productname>NSS</productname> database, or if the database itself + is password protected. If multiple different objects are password + protected, the same password is used for all. + </para> + </listitem> + </itemizedlist>
Is this a statement about NSS databases (which I don't think it is) or
about the fact that we'll just use the password provided for all
attempts to decrypt something we need in the database? Assuming the
latter, seems like we could reword this to be a bit more clear.
Maybe:
All attempts to decrypt objects which are password protected in the
database will use this password.
?
@@ -2620,9 +2791,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name); + For <productname>NSS</productname>, there is one struct available under + the name "NSS", and it returns a pointer to the + <productname>NSS</productname> <literal>PRFileDesc</literal>.
... SSL PRFileDesc associated with the connection, no?
+++ b/doc/src/sgml/runtime.sgml @@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \ </para> </sect2>+ <sect2 id="nss-certificate-database"> + <title>NSS Certificate Databases</title> + + <para> + When using <productname>NSS</productname>, all certificates and keys must + be loaded into an <productname>NSS</productname> certificate database. + </para> + + <para> + To create a new <productname>NSS</productname> certificate database and + load the certificates created in <xref linkend="ssl-certificate-creation" />, + use the following <productname>NSS</productname> commands: +<programlisting> +certutil -d "sql:server.db" -N --empty-password +certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C" +certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C" +</programlisting> + This will give the certificate the filename as the nickname identifier in + the database which is created as <filename>server.db</filename>. + </para> + <para> + Then load the server key, which require converting it to
*requires
Subject: [PATCH v30 6/9] nss: Support NSS in pgcrypto +++ b/doc/src/sgml/pgcrypto.sgml <row> <entry>Blowfish</entry> <entry>yes</entry> <entry>yes</entry> + <entry>yes</entry> </row>
Maybe this should mention that it's with the built-in implementation as
blowfish isn't available from NSS?
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
Surely CAST5 from the above should be removed, since it's given its own
entry now?
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid <orderedlist> <listitem> <para> - Any digest algorithm <productname>OpenSSL</productname> supports + Any digest algorithm <productname>OpenSSL</productname> and + <productname>NSS</productname> supports is automatically picked up.
*or? Maybe something more specific though- "Any digest algorithm
included with the library that PostgreSQL is compiled with is
automatically picked up." ?
Subject: [PATCH v30 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
*uses
Subject: [PATCH v30 8/9] nss: Support NSS in cryptohash +++ b/src/common/cryptohash_nss.c + /* + * Initialize our own NSS context without a database backing it. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + status = NSS_NoDB_Init(".");
We take some pains to use NSS_InitContext elsewhere.. Are we sure that
we should be using NSS_NoDB_Init here..?
Just a, well, not so quick read-through. Generally it's looking pretty
good to me. Will see about playing with it this week.
Thanks!
Stephen
On 22 Mar 2021, at 00:49, Stephen Frost <sfrost@snowman.net> wrote:
Greetings,
Thanks for the review! Below is a partial response, I haven't had time to
address all your review comments yet but I wanted to submit a rebased patchset
directly since the current version doesn't work after recent changes in the
tree. I will address the remaining comments tomorrow or the day after.
This rebase also includes a fix for pgtls_init which was sent offlist by Jacob.
The changes in pgtls_init can potentially be used to initialize the crypto
context for NSS to clean up this patch, Jacob is currently looking at that.
Subject: [PATCH v30 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. The implementation is donemaybe add 'using NSS' to that first sentence. ;)
Fixed.
+++ b/src/backend/libpq/auth.c @@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port) { int status_check_usermap = STATUS_ERROR;+#if defined(USE_OPENSSL) Assert(port->ssl); +#elif defined(USE_NSS) + /* TODO: should we rename pr_fd to ssl, to keep consistency? */ + Assert(port->pr_fd); +#else + Assert(false); +#endifHaving thought about this TODO item for a bit, I tend to think it's
better to keep them distinct.
I agree, which is why the TODO comment was there in the first place. I've
removed the comment now.
They aren't the same and it might not be
clear what's going on if one was to somehow mix them (at least if pr_fd
continues to sometimes be a void*, but I wonder why that's being
done..? more on that later..).
To paraphrase from a later in this email, there are collisions between nspr and
postgres on things like BITS_PER_BYTE, and there were also collisions on basic
types until I learned about NO_NSPR_10_SUPPORT. By moving the juggling of this
into common/nss.h we can use proper types without introducing that pollution
everywhere. I will address these places.
+++ b/src/backend/libpq/be-secure-nss.c[...]
+/* default init hook can be overridden by a shared library */ +static void default_nss_tls_init(bool isServerStart); +nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;+static PRDescIdentity pr_id; + +static PRIOMethods pr_iomethods;Happy to be told I'm missing something, but the above two variables seem
to only be used in init_iolayer.. is there a reason they're declared
here instead of just being declared in that function?
They must be there since NSPR doesn't copy these but reference them.
+ /* + * 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. + */Should we pull these error messages out into another header so that
they're in one place to make sure they're kept consistent, if we really
want to put the effort in to keep them the same..? I'm not 100% sure
that it's actually necessary to do so, but defining these in one place
would help maintain this if we want to. Also alright with just keeping
the comment, not that big of a deal.
It might make sense to pull them into common/nss.h, but seeing the error
message right there when reading the code does IMO make it clearer so it's a
doubleedged sword. Not sure what is the best option, but I'm not married to
the current solution so if there is consensus to pull them out somewhere I'm
happy to do so.
+int +be_tls_open_server(Port *port) +{ + SECStatus status; + PRFileDesc *model; + PRFileDesc *pr_fd;pr_fd here is materially different from port->pr_fd, no? As in, one is
the NSS raw TCP fd while the other is the SSL fd, right? Maybe we
should use two different variable names to try and make sure they don't
get confused? Might even set this to NULL after we are done with it
too.. Then again, I see later on that when we do the dance with the
'model' PRFileDesc that we just use the same variable- maybe we should
do that? That is, just get rid of this 'pr_fd' and use port->pr_fd
always?
Hmm, I think you're right. I will try that for the next patchset version.
+ /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. The documentation doesn't however clarify + * from which version this is holds true, so let's perform the potentially + * superfluous initialization anyways to avoid crashing on older versions + * of NSPR, as there is no difference in overhead. The NSS documentation + * still states that PR_Init must be called in some way (implicitly or + * explicitly). + * + * The below parameters are what the implicit initialization would've done + * for us, and should work even for older versions where it might not be + * done automatically. The last parameter, maxPTDs, is set to various + * values in other codebases, but has been unused since NSPR 2.1 which was + * released sometime in 1998. In current versions of NSPR all parameters + * are ignored. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ ); + + /* + * The certificate path (configdir) must contain a valid NSS database. If + * the certificate path isn't a valid directory, NSS will fall back on the + * system certificate database. If the certificate path is a directory but + * is empty then the initialization will fail. On the client side this can + * be allowed for any sslmode but the verify-xxx ones. + * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side + * we won't allow this to fail however, as we require the certificate and + * key to exist. + * + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456The above seems to indicate that we will be requiring at least 3.12,
right? Yet above we have code to work with NSPR versions before 2.1?
Well, not really. The comment tries to explain the rationale for the
parameters passed. Clearly the comment could be improved to make that point
clearer.
Maybe we should put a stake in the ground that says "we only support
back to version X of NSS", test with that and a few more recent versions
and the most recent, and then rip out anything that's needed for
versions which are older than that?
Yes, right now there is very little in the patch which caters for old versions,
the PR_Init call might be one of the few offenders. There has been discussion
upthread about settling for a required version, combining the insights learned
there with a survey of which versions are commonly available packaged.
Once we settle on a version we can confirm if PR_Init is/isn't needed and
remove all traces of it if not.
I have a pretty hard time imagining that someone is going to want to build PG
v14 w/ NSS 2.0 ...
Let alone compiling 2.0 at all on a recent system..
+ { + char *ciphers, + *c; + + char *sep = ":;, "; + PRUint16 ciphercode; + const PRUint16 *nss_ciphers; + + /* + * If the user has specified a set of preferred cipher suites we start + * by turning off all the existing suites to avoid the risk of down- + * grades to a weaker cipher than expected. + */ + nss_ciphers = SSL_GetImplementedCiphers(); + for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++) + SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE); + + ciphers = pstrdup(SSLCipherSuites); + + for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep)) + { + if (!pg_find_cipher(c, &ciphercode)) + { + status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + return -1; + } + } + }Maybe I'm a bit confused, but doesn't pg_find_cipher return *true* when
a cipher is found, and therefore the '!' above is saying "if we don't
find a matching cipher, then run the code to set the cipher ...".
Hmm, yes thats broken. Fixed.
Also- we don't seem to complain at all about a cipher being specified that we
don't find? Guess I would think that we might want to throw a WARNING in such
a case, but I could possibly be convinced otherwise.
No, I think you're right, we should throw WARNING there or possibly even a
higher elevel. Should that be a COMMERROR even?
Kind of wonder just what happens with the current code, I'm guessing ciphercode
is zero and therefore doesn't complain but also doesn't do what we want. I
wonder if there's a way to test this?
We could extend the test suite to set ciphers in postgresql.conf, I'll give it
a go.
I do think we should probably throw an error if we end up with *no*
ciphers being set, which doesn't seem to be happening here..?
Yeah, that should be a COMMERROR. Fixed.
+ /* + * Set up the custom IO layer. + */Might be good to mention that the IO Layer is what sets up the
read/write callbacks to be used.
Good point, will do in the next version of the patchset.
+ port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + { + ereport(COMMERROR, + (errmsg("unable to initialize"))); + return -1; + }Maybe a comment and a better error message for this?
Will do.
+ PR_Close(model);
This might deserve one also, the whole 'model' construct is a bit
different. :)
Agreed. will do.
+ port->ssl_in_use = true; + + /* Register out shutdown callback */*our
Fixed.
+int +be_tls_get_cipher_bits(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.effectiveKeyBits; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return 0; +}It doesn't have to be much, but I, at least, do prefer to see
function-header comments. :) Not that the OpenSSL code has them
consistently, so obviously not that big of a deal. Goes for a number of
the functions being added.
No disagreement from me, I've added comments on a few more functions and will
continue to go over the patchset to add them everywhere. Some of these
comments are pretty uninteresting and could do with some wordsmithing.
+ /* Found a CN, ecode and copy it into a newly allocated buffer */
*decode
Fixed.
+static PRInt32 +pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRStatus +pg_ssl_close(PRFileDesc *fd) +{ + /* + * Disconnect our private Port from the fd before closing out the stack. + * (Debug builds of NSPR will assert if we do not.) + */ + fd->secret = NULL; + return PR_GetDefaultIOMethods()->close(fd); +}Regarding these, I find myself wondering how they're different from the
defaults..? I mean, the above just directly called
PR_GetDefaultIOMethods() to then call it's close() function- are the
fd->lower_methods->recv/send not the default methods? I don't quite get
what the point is from having our own callbacks here if they just do
exactly what the defaults would do (or are there actually no defined
defaults and you have to provide these..?).
It's really just to cope with debug builds of NSPR which assert that fd->secret
is null before closing.
+/* + * 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, const char *guc_name)guc_name isn't actually used in this function..? Is there some reason
to keep it or is it leftover?
It's a leftover from when the function was doing error reporting, fixed.
Also, I get that they do similar jobs and that one is in the frontend
and the other is in the backend, but I'm not a fan of having two
'ssl_protocol_version_to_nss()'s functions that take different argument
types but have exact same name and do functionally different things..
Good point, I'll change that.
+++ b/src/backend/utils/misc/guc.c @@ -4377,6 +4381,18 @@ static struct config_string ConfigureNamesString[] = check_canonical_path, assign_pgstat_temp_directory, NULL },+#ifdef USE_NSS + { + {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the NSS certificate database."), + NULL + }, + &ssl_database, + "", + NULL, NULL, NULL + }, +#endifWe don't #ifdef out the various GUCs even if SSL isn't compiled in, so
it doesn't seem quite right to be doing so here? Generally speaking,
GUCs that we expect people to use (rather than debugging ones and such)
are typically always built, even if we don't build support for that
capability, so we can throw a better error message than just some ugly
syntax or parsing error if we come across one being set in a non-enabled
build.
Of course, fixed.
+++ 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 bse used if code is compiled with NSS support.*be
Fixed.
+++ b/src/include/libpq/libpq-be.h @@ -200,6 +200,10 @@ typedef struct Port SSL *ssl; X509 *peer; #endif + +#ifdef USE_NSS + void *pr_fd; +#endif } Port;Given this is under a #ifdef USE_NSS, does it need to be / should it
really be a void*?
It's to avoid the same BITS_PER_BYTE collision discussed elsewhere in this
email.
+++ b/src/interfaces/libpq/fe-connect.c @@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */ offsetof(struct pg_conn, target_session_attrs)},+ {"cert_database", NULL, NULL, NULL, + "CertificateDatabase", "", 64, + offsetof(struct pg_conn, cert_database)},I mean, maybe nitpicking here, but all the other SSL stuff is
'sslsomething' and the backend version of this is 'ssl_database', so
wouldn't it be more consistent to have this be 'ssldatabase'?
Thats a good point, I was clearly Stockholm syndromed since I hadn't reflected
on that but it's clearly wrong. Will fix.
+++ b/src/interfaces/libpq/fe-secure-nss.c + * This logic exist in NSS as well, but it's only available for when there is*exists
Fixed.
+ /* + * 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);See same comment I made above- and also there's a comment earlier in
this file that we don't need to PR_Init() even ...
Right, once we can confirm that the minimum required versions are past the
PR_Init dependency then we should remove all of these calls. If we can't
remove the calls, the comments should be updated to reflect why they are there.
+ { + conn->nss_context = 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 create certificate database: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + }That error message seems a bit ... off? Surely we aren't trying to
actually create a certificate database here?
Not really no, it does set up a transient database structure for the duration
of the connection AFAIK but thats clearly not the level of detail we should be
giving users. I've reworded to indicate that NSS init failed, and ideally the
pg_SSLerrmessage call will provide appropriate detail.
+ /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure cipher policy: %s"), + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + }Probably good to pull over at least some parts of the comments made in
the backend code about SetDomesticPolicy() actually enabling everything
(just like all the policies apparently do)...
Good point, will do.
+ /* + * 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->cert_database && conn->sslmode[0] == 'v') + { + status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\""); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), + ca_trust_name, + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + }Maybe have something a bit more here about "maybe you should specifify a
cert_database" or such?
Good point, will expand with more detail.
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0) + { + int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version); + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"), + conn->ssl_max_protocol_version); + return -1; + } + + desired_range.max = ssl_max_ver; + }In the backend code, we have an additional check to make sure they
didn't set the min version higher than the max.. should we have that
here too? Either way, seems like we should be consistent.
We already test that in src/interfaces/libpq/fe-connect.c.
+ * The model can now we closed as we've applied the settings of the model
*be
Fixed.
+ * onto the real socket. From hereon we should only use conn->pr_fd.
*here on
Fixed.
Similar comments to the backend code- should we just always use
conn->pr_fd? Or should we rename pr_fd to something else?
Renaming is probably not a bad idea, will fix.
+ /* + * Specify which hostname we are expecting to talk to. This is required, + * albeit mostly applies to when opening a connection to a traditional + * http server it seems. + */ + SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);We should probably also set SNI, if available (NSS 3.12.6 it seems?),
since it looks like that's going to be added to the OpenSSL code.
Good point, will do.
+ do + { + status = SSL_ForceHandshake(conn->pr_fd); + } + while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);We don't seem to have this loop in the backend code.. Is there some
reason that we don't? Is it possible that we need to have a loop here
too? I recall in the GSS encryption code there were definitely things
during setup that had to be looped back over on both sides to make sure
everything was finished ...
Off the cuff I can't remember, will look into it.
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;Seems a bit grotty to do this (though I see that the OpenSSL code does
too ... at least there we have a comment though, maybe add one here?).
I would have thought we'd actually do strcmp()'s like above.
That's admittedly copied from the OpenSSL code, and I agree that it's a bit too
clever. Replaced with plain strcmp's to improve readability in both places it
occurred.
+ /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +}Is there never a reason someone might want the pointer returned by
NSS_InitContext? I don't know that there is but it might be something
to consider (we could even possibly have our own structure returned by
this function which includes both, maybe..?). Not sure if there's a
sensible use-case for that or not just wanted to bring it up as it's
something I asked myself while reading through this patch.
Not sure I understand what you're asking for here, did you mean "is there ever
a reason"?
+ if (strcmp(attribute_name, "protocol") == 0) + { + switch (channel.protocolVersion) + { +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + case SSL_LIBRARY_VERSION_TLS_1_3: + return "TLSv1.3"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + case SSL_LIBRARY_VERSION_TLS_1_2: + return "TLSv1.2"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + case SSL_LIBRARY_VERSION_TLS_1_1: + return "TLSv1.1"; +#endif + case SSL_LIBRARY_VERSION_TLS_1_0: + return "TLSv1.0"; + default: + return "unknown"; + } + }Not sure that it really matters, but this seems like it might be useful
to have as its own function... Maybe even a data structure that both
functions use just in oppostie directions. Really minor tho. :)
I suppose that wouldn't be a bad thing, will fix.
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index c601071838..7f10da3010 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) } #endif /* USE_OPENSSL */+#ifndef USE_NSS + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return NULL; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + return; +} + +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + return NULL; +} +#endif /* USE_NSS */Isn't this '!USE_NSS'?
Technically it is, but using just /* USE_NSS */ is consistent with the rest of
blocks in the file.
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 0c9e95f1a7..f15af39222 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -383,6 +383,7 @@ struct pg_conn char *sslrootcert; /* root certificate filename */ char *sslcrl; /* certificate revocation list filename */ char *sslcrldir; /* certificate revocation list directory name */ + char *cert_database; /* NSS certificate/key database */ char *requirepeer; /* required peer credentials for local sockets */ char *gssencmode; /* GSS mode (require,prefer,disable) */ char *krbsrvname; /* Kerberos service name */ @@ -507,6 +508,28 @@ struct pg_conn * OpenSSL version changes */ #endif #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. + */I'm a bit confused- what are the conflicts being caused here..?
Certainly under USE_OPENSSL we use the actual OpenSSL types..
It's referring to collisions with for example BITS_PER_BYTE which is defined
both by postgres and nspr. Since writing this I've introduced src/common/nss.h
to handle it in a single place, so we can indeed use the proper types without
polluting the file. Fixed.
Subject: [PATCH v30 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/<lib>
modules for the implementation details.No changes are done to the actual tests, this only change how setup and
teardown is performed.Presumably this could be committed ahead of the main NSS support?
Correct, I think this has merits even if NSS support is ultimately rejected.
Subject: [PATCH v30 4/9] nss: pg_strong_random support +++ b/src/port/pg_strong_random.c +bool +pg_strong_random(void *buf, size_t len) +{ + NSSInitParameters params; + NSSInitContext *nss_context; + SECStatus status; + + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = 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 = PK11_GenerateRandom(buf, len); + NSS_ShutdownContext(nss_context); + + if (status == SECSuccess) + return true; + + return false; +} + +#else /* not USE_OPENSSL, USE_NSS or WIN32 */I don't know that it's an issue, but do we actually need to init the NSS
context and shut it down every time..?
We need to have a context, and we should be able to set it like how the WIN32
code sets hProvider. I don't remember if there was a reason against that, will
revisit.
/*
* Without OpenSSL or Win32 support, just read /dev/urandom ourselves.*or NSS
Fixed.
Subject: [PATCH v30 5/9] nss: Documentation +++ b/doc/src/sgml/acronyms.sgml @@ -684,6 +717,16 @@ </listitem> </varlistentry>+ <varlistentry> + <term><acronym>TLS</acronym></term> + <listitem> + <para> + <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security"> + Transport Layer Security</ulink> + </para> + </listitem> + </varlistentry>We don't have this already..? Surely we should..
We really should, especially since we've had <acronym>TLS</acronym> in
config.sgml since 2014 (c6763156589). That's another small piece that could be
committed on it's own to cut down the size of this patchset (even if only by a
tiny amount).
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 967de73596..1608e9a7c7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1272,6 +1272,23 @@ include_dir 'conf.d' </listitem> </varlistentry>+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database"> + <term><varname>ssl_database</varname> (<type>string</type>) + <indexterm> + <primary><varname>ssl_database</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Specifies the name of the file containing the server certificates and + keys when using <productname>NSS</productname> for <acronym>SSL</acronym> + connections. This parameter can only be set in the + <filename>postgresql.conf</filename> file or on the server command + line.*SSL/TLS maybe?
Fixed.
@@ -1288,7 +1305,9 @@ include_dir 'conf.d' connections using TLS version 1.2 and lower are affected. There is currently no setting that controls the cipher choices used by TLS version 1.3 connections. The default value is - <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a + <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have + been built with <productname>OpenSSL</productname> as the + <acronym>SSL</acronym> library. The default is usually a reasonable choice unless you have specific security requirements. </para>Shouldn't we say something here wrt NSS?
We should, but I'm not entirely what just yet. Need to revisit that.
@@ -1490,8 +1509,11 @@ include_dir 'conf.d' <para> Sets an external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained. By - default, this parameter is empty, which means the built-in prompting - mechanism is used. + default, this parameter is empty. When the server is using + <productname>OpenSSL</productname>, this means the built-in prompting + mechanism is used. When using <productname>NSS</productname>, there is + no default prompting so a blank callback will be used returning an + empty password. </para>Maybe we should point out here that this requires the database to not
require a password..? So if they have one, they need to set this, or
maybe we should provide a default one..
I've added a sentence on not using a password for the cert database. I'm not
sure if providing a default one is a good idea but it's no less insecure than
having no password really..
+++ b/doc/src/sgml/libpq.sgml +<synopsis> +PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void); +</synopsis> + </para> + + <para> + <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the + server was compiled with <productname>nss</productname> support. + </para>We should try to be consistent- above should be NSS, not nss.
Fixed.
+ <listitem> + <para> + <productname>NSS</productname>: specifying the parameter is required + in case any password protected items are referenced in the + <productname>NSS</productname> database, or if the database itself + is password protected. If multiple different objects are password + protected, the same password is used for all. + </para> + </listitem> + </itemizedlist>Is this a statement about NSS databases (which I don't think it is) or
about the fact that we'll just use the password provided for all
attempts to decrypt something we need in the database?
Correct.
Assuming the
latter, seems like we could reword this to be a bit more clear.Maybe:
All attempts to decrypt objects which are password protected in the
database will use this password.
Agreed, fixed.
@@ -2620,9 +2791,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name); + For <productname>NSS</productname>, there is one struct available under + the name "NSS", and it returns a pointer to the + <productname>NSS</productname> <literal>PRFileDesc</literal>.... SSL PRFileDesc associated with the connection, no?
I was trying to be specific that it's an NSS-defined structure and not a
PostgreSQL one which is returned. Fixed.
+++ b/doc/src/sgml/runtime.sgml @@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \ </para> </sect2>+ <sect2 id="nss-certificate-database"> + <title>NSS Certificate Databases</title> + + <para> + When using <productname>NSS</productname>, all certificates and keys must + be loaded into an <productname>NSS</productname> certificate database. + </para> + + <para> + To create a new <productname>NSS</productname> certificate database and + load the certificates created in <xref linkend="ssl-certificate-creation" />, + use the following <productname>NSS</productname> commands: +<programlisting> +certutil -d "sql:server.db" -N --empty-password +certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C" +certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C" +</programlisting> + This will give the certificate the filename as the nickname identifier in + the database which is created as <filename>server.db</filename>. + </para> + <para> + Then load the server key, which require converting it to*requires
Fixed.
Subject: [PATCH v30 6/9] nss: Support NSS in pgcrypto +++ b/doc/src/sgml/pgcrypto.sgml <row> <entry>Blowfish</entry> <entry>yes</entry> <entry>yes</entry> + <entry>yes</entry> </row>Maybe this should mention that it's with the built-in implementation as
blowfish isn't available from NSS?
Fixed by adding a Note item.
<row>
<entry>DES/3DES/CAST5</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>Surely CAST5 from the above should be removed, since it's given its own
entry now?
Indeed, fixed.
@@ -1241,7 +1260,8 @@ gen_random_uuid() returns uuid <orderedlist> <listitem> <para> - Any digest algorithm <productname>OpenSSL</productname> supports + Any digest algorithm <productname>OpenSSL</productname> and + <productname>NSS</productname> supports is automatically picked up.*or? Maybe something more specific though- "Any digest algorithm
included with the library that PostgreSQL is compiled with is
automatically picked up." ?
Good point, thats better. Fixed.
Subject: [PATCH v30 7/9] nss: Support NSS in sslinfo
Since sslinfo to a large extent use the be_tls_* API this mostly
*uses
Fixed.
Subject: [PATCH v30 8/9] nss: Support NSS in cryptohash +++ b/src/common/cryptohash_nss.c + /* + * Initialize our own NSS context without a database backing it. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + status = NSS_NoDB_Init(".");We take some pains to use NSS_InitContext elsewhere.. Are we sure that
we should be using NSS_NoDB_Init here..?
No, we should probably be using NSS_InitContext. Will fix.
Just a, well, not so quick read-through. Generally it's looking pretty
good to me. Will see about playing with it this week.
Thanks again for reviewing, another version which addresses the remaining
issues will be posted soon but I wanted to get this out to give further reviews
something that properly works.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v31-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v31-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 6431a7863cfa7f8374da6a80375760615b651fed Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v31 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 | 342 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 582 insertions(+), 163 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 4b53fdf6c0..8d4afa9dd0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -98,6 +262,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -130,19 +302,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -168,14 +361,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 6124d9ea23..a5513db71f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 103;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 104;
}
else
{
- plan tests => 100;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,70 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +151,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -111,95 +171,118 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -212,14 +295,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,20 +320,20 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,21 +343,21 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -287,14 +370,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -303,11 +386,11 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -315,16 +398,16 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -345,32 +428,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -386,32 +487,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -451,18 +563,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -475,10 +588,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -490,7 +606,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -514,24 +630,28 @@ test_connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..801c945e88 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index e59554f85b..da25645d25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -202,46 +201,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v31-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v31-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 547723a97e22d130e23a27d9249ce12caad37a18 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v31 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index bfada03d3e..6124d9ea23 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -574,7 +536,5 @@ test_connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..e59554f85b 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,14 +164,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -159,20 +186,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -182,13 +225,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v31-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v31-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 520f608ff0a2390901c87f693f8caa97d3232cf1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v31 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
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1431 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1094 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
15 files changed, 2870 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..3d51a7c9b8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 994251e7d9..0512d058e8 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2801,7 +2801,13 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2dfa85f417
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1431 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+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 char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initialize")));
+ return -1;
+ }
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index bb603ad209..43a5c5de42 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3b36a31a47..c1e335e2bc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4347,7 +4347,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4415,6 +4419,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4443,8 +4457,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 30fb4e613d..cad45c7612 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -200,6 +200,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +286,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +296,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b20deeb555..14bb1d20a1 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -87,6 +87,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 53b354abb2..56129a0179 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -341,6 +341,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..92afaaa660
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1094 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->cert_database &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database &&
+ strlen(conn->cert_database) > 0 &&
+ cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index cee42d4843..fd5a0e99a3 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -643,6 +643,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6374ec657a..65391d9a92 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 */
+#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 *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -522,6 +527,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -783,7 +810,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
v31-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v31-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 371622b4852322e51f543a295ed989d7b777bf93 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v31 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 408 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 06ad9aeb71..7d070a758b 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 92193f35fb..6296d32ff6 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 5e2255a2f5..c50ea54530 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -325,6 +325,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -337,6 +343,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -914,6 +923,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 2aca882a2b..ed8aa00bcb 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -55,6 +55,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index a184404e21..a7816adfa2 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -134,6 +134,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -198,12 +203,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -261,12 +273,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -434,9 +453,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -466,6 +490,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 710f26f8ab..e99550ff45 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -300,10 +300,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -493,6 +496,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -545,6 +549,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1003,6 +1014,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v31-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v31-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From f1e2486b6f902bb4a140f456c34aacaed7cc5912 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v31 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v31-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v31-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From b3d3664e6bbe6bccc099dd6aa960a2e12fbd1a36 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v31 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v31-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v31-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From ed657706efd110a9824ca88ec9ef147975821574 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v31 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 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
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.21.1 (Apple Git-122.3)
v31-0005-nss-Documentation.patchapplication/octet-stream; name=v31-0005-nss-Documentation.patch; x-unix-mode=0644Download
From 327d7e20625982a17864c1a810e57200eded3ef5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v31 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 | 43 +++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 336 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 472 insertions(+), 92 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5679b40dd5..151c950b31 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1306,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1510,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index be674fbaa9..de9f7a133d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,43 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1845,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1875,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,18 +1910,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1808,8 +1968,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1824,8 +1984,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2587,6 +2747,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2610,9 +2772,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2639,6 +2807,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8560,6 +8732,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8586,6 +8763,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..bb13f81326 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v31-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v31-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 38ec324982018adb673f6e57488752dd0d597a6d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v31 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.21.1 (Apple Git-122.3)
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
On 22 Mar 2021, at 00:49, Stephen Frost <sfrost@snowman.net> wrote:
Thanks for the review! Below is a partial response, I haven't had time to
address all your review comments yet but I wanted to submit a rebased patchset
directly since the current version doesn't work after recent changes in the
tree. I will address the remaining comments tomorrow or the day after.
Great, thanks!
This rebase also includes a fix for pgtls_init which was sent offlist by Jacob.
The changes in pgtls_init can potentially be used to initialize the crypto
context for NSS to clean up this patch, Jacob is currently looking at that.
Ah, cool, sounds good.
They aren't the same and it might not be
clear what's going on if one was to somehow mix them (at least if pr_fd
continues to sometimes be a void*, but I wonder why that's being
done..? more on that later..).To paraphrase from a later in this email, there are collisions between nspr and
postgres on things like BITS_PER_BYTE, and there were also collisions on basic
types until I learned about NO_NSPR_10_SUPPORT. By moving the juggling of this
into common/nss.h we can use proper types without introducing that pollution
everywhere. I will address these places.
Ah, ok, and great, that sounds good.
+++ b/src/backend/libpq/be-secure-nss.c[...]
+/* default init hook can be overridden by a shared library */ +static void default_nss_tls_init(bool isServerStart); +nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;+static PRDescIdentity pr_id; + +static PRIOMethods pr_iomethods;Happy to be told I'm missing something, but the above two variables seem
to only be used in init_iolayer.. is there a reason they're declared
here instead of just being declared in that function?They must be there since NSPR doesn't copy these but reference them.
Ah, ok, interesting.
+ /* + * 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. + */Should we pull these error messages out into another header so that
they're in one place to make sure they're kept consistent, if we really
want to put the effort in to keep them the same..? I'm not 100% sure
that it's actually necessary to do so, but defining these in one place
would help maintain this if we want to. Also alright with just keeping
the comment, not that big of a deal.It might make sense to pull them into common/nss.h, but seeing the error
message right there when reading the code does IMO make it clearer so it's a
doubleedged sword. Not sure what is the best option, but I'm not married to
the current solution so if there is consensus to pull them out somewhere I'm
happy to do so.
My thought was to put them into some common/ssl.h or something along
those lines but I don't see it as a big deal either way really. You
make a good point that having the error message there when reading the
code is nice.
Maybe we should put a stake in the ground that says "we only support
back to version X of NSS", test with that and a few more recent versions
and the most recent, and then rip out anything that's needed for
versions which are older than that?Yes, right now there is very little in the patch which caters for old versions,
the PR_Init call might be one of the few offenders. There has been discussion
upthread about settling for a required version, combining the insights learned
there with a survey of which versions are commonly available packaged.Once we settle on a version we can confirm if PR_Init is/isn't needed and
remove all traces of it if not.
I don't really see this as all that hard to do- I'd suggest we look at
what systems someone might reasonably deploy v14 on. To that end, I'd
say "only systems which are presently supported", so: RHEL7+, Debian 9+,
Ubuntu 16.04+. Looking at those, I see:
Ubuntu 16.04: 3.28.4
RHEL6: v3.28.4
Debian: 3.26.2
I have a pretty hard time imagining that someone is going to want to build PG
v14 w/ NSS 2.0 ...Let alone compiling 2.0 at all on a recent system..
Indeed, and given the above, it seems entirely reasonable to make the
requirement be NSS v3+, no? I wouldn't be against making that even
tighter if we thought it made sense to do so.
Also- we don't seem to complain at all about a cipher being specified that we
don't find? Guess I would think that we might want to throw a WARNING in such
a case, but I could possibly be convinced otherwise.No, I think you're right, we should throw WARNING there or possibly even a
higher elevel. Should that be a COMMERROR even?
I suppose the thought I was having was that we might want to allow some
string that covered all the OpenSSL and NSS ciphers that someone feels
comfortable with and we'd just ignore the ones that don't make sense for
the particular library we're currently built with. Making it a
COMMERROR seems like overkill and I'm not entirely sure we actually want
any warning since we might then be constantly bleating about it.
Kind of wonder just what happens with the current code, I'm guessing ciphercode
is zero and therefore doesn't complain but also doesn't do what we want. I
wonder if there's a way to test this?We could extend the test suite to set ciphers in postgresql.conf, I'll give it
a go.
That'd be great, thanks!
I do think we should probably throw an error if we end up with *no*
ciphers being set, which doesn't seem to be happening here..?Yeah, that should be a COMMERROR. Fixed.
I do think it makes sense to throw a COMMERROR here since the connection
is going to end up failing anyway.
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRStatus +pg_ssl_close(PRFileDesc *fd) +{ + /* + * Disconnect our private Port from the fd before closing out the stack. + * (Debug builds of NSPR will assert if we do not.) + */ + fd->secret = NULL; + return PR_GetDefaultIOMethods()->close(fd); +}Regarding these, I find myself wondering how they're different from the
defaults..? I mean, the above just directly called
PR_GetDefaultIOMethods() to then call it's close() function- are the
fd->lower_methods->recv/send not the default methods? I don't quite get
what the point is from having our own callbacks here if they just do
exactly what the defaults would do (or are there actually no defined
defaults and you have to provide these..?).It's really just to cope with debug builds of NSPR which assert that fd->secret
is null before closing.
And we have to override the recv/send functions for this too..? Sorry,
my comment wasn't just about the close() method but about the others
too.
+ /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +}Is there never a reason someone might want the pointer returned by
NSS_InitContext? I don't know that there is but it might be something
to consider (we could even possibly have our own structure returned by
this function which includes both, maybe..?). Not sure if there's a
sensible use-case for that or not just wanted to bring it up as it's
something I asked myself while reading through this patch.Not sure I understand what you're asking for here, did you mean "is there ever
a reason"?
Eh, poor wording on my part. You're right, the question, reworded
again, was "Would someone want to get the context returned by
NSS_InitContext?". If we think there's a reason that someone might want
that context then perhaps we should allow getting it, in addition to the
pr_fd. If there's really no reason to ever want the context from
NSS_InitContext then what you have here where we're returning pr_fd is
probably fine.
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index c601071838..7f10da3010 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) } #endif /* USE_OPENSSL */+#ifndef USE_NSS + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return NULL; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + return; +} + +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + return NULL; +} +#endif /* USE_NSS */Isn't this '!USE_NSS'?
Technically it is, but using just /* USE_NSS */ is consistent with the rest of
blocks in the file.
Hrmpf. I guess it seems a bit confusing to me to have to go find the
opening #ifndef to realize that it's actally !USE_NSS.. In other words,
I would think we'd actually want to fix all of these, heh. I only
actually see one case on a quick grep where it's wrong for USE_OPENSSL
and so that doesn't seem like it's really a precedent and is more of a
bug. We certainly say 'not OPENSSL' in one place today too and also
have a number of places where we have: #endif ... /* ! WHATEVER */.
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 0c9e95f1a7..f15af39222 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -383,6 +383,7 @@ struct pg_conn char *sslrootcert; /* root certificate filename */ char *sslcrl; /* certificate revocation list filename */ char *sslcrldir; /* certificate revocation list directory name */ + char *cert_database; /* NSS certificate/key database */ char *requirepeer; /* required peer credentials for local sockets */ char *gssencmode; /* GSS mode (require,prefer,disable) */ char *krbsrvname; /* Kerberos service name */ @@ -507,6 +508,28 @@ struct pg_conn * OpenSSL version changes */ #endif #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. + */I'm a bit confused- what are the conflicts being caused here..?
Certainly under USE_OPENSSL we use the actual OpenSSL types..It's referring to collisions with for example BITS_PER_BYTE which is defined
both by postgres and nspr. Since writing this I've introduced src/common/nss.h
to handle it in a single place, so we can indeed use the proper types without
polluting the file. Fixed.
Great, thanks!
Subject: [PATCH v30 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/<lib>
modules for the implementation details.No changes are done to the actual tests, this only change how setup and
teardown is performed.Presumably this could be committed ahead of the main NSS support?
Correct, I think this has merits even if NSS support is ultimately rejected.
Ok- could you break it out on to its own thread and I'll see about
committing it soonish, to get it out of the way?
Subject: [PATCH v30 5/9] nss: Documentation +++ b/doc/src/sgml/acronyms.sgml @@ -684,6 +717,16 @@ </listitem> </varlistentry>+ <varlistentry> + <term><acronym>TLS</acronym></term> + <listitem> + <para> + <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security"> + Transport Layer Security</ulink> + </para> + </listitem> + </varlistentry>We don't have this already..? Surely we should..
We really should, especially since we've had <acronym>TLS</acronym> in
config.sgml since 2014 (c6763156589). That's another small piece that could be
committed on it's own to cut down the size of this patchset (even if only by a
tiny amount).
Ditto on this. :)
@@ -1288,7 +1305,9 @@ include_dir 'conf.d' connections using TLS version 1.2 and lower are affected. There is currently no setting that controls the cipher choices used by TLS version 1.3 connections. The default value is - <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a + <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have + been built with <productname>OpenSSL</productname> as the + <acronym>SSL</acronym> library. The default is usually a reasonable choice unless you have specific security requirements. </para>Shouldn't we say something here wrt NSS?
We should, but I'm not entirely what just yet. Need to revisit that.
Not sure if we really want to do this but at least with ssllabs.com,
postgresql.org gets an 'A' rating with this set:
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA256
ECDHE-RSA-AES128-SHA256
ECDHE-ECDSA-AES128-SHA
ECDHE-RSA-AES256-SHA384
ECDHE-RSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA384
ECDHE-ECDSA-AES256-SHA
ECDHE-RSA-AES256-SHA
DHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA
ECDHE-ECDSA-DES-CBC3-SHA
ECDHE-RSA-DES-CBC3-SHA
EDH-RSA-DES-CBC3-SHA
AES128-GCM-SHA256
AES256-GCM-SHA384
AES128-SHA256
AES256-SHA256
AES128-SHA
AES256-SHA
DES-CBC3-SHA
!DSS
Which also seems kinda close to what the default when built with OpenSSL
ends up being? Thought the ssllabs report does list which ones it
thinks are weak and so we might consider excluding those by default too:
@@ -1490,8 +1509,11 @@ include_dir 'conf.d' <para> Sets an external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained. By - default, this parameter is empty, which means the built-in prompting - mechanism is used. + default, this parameter is empty. When the server is using + <productname>OpenSSL</productname>, this means the built-in prompting + mechanism is used. When using <productname>NSS</productname>, there is + no default prompting so a blank callback will be used returning an + empty password. </para>Maybe we should point out here that this requires the database to not
require a password..? So if they have one, they need to set this, or
maybe we should provide a default one..I've added a sentence on not using a password for the cert database. I'm not
sure if providing a default one is a good idea but it's no less insecure than
having no password really..
I was meaning a default callback to prompt, not sure if that was clear.
Just a, well, not so quick read-through. Generally it's looking pretty
good to me. Will see about playing with it this week.Thanks again for reviewing, another version which addresses the remaining
issues will be posted soon but I wanted to get this out to give further reviews
something that properly works.
Fantastic, thanks again!
Stephen
On Tue, 2021-03-23 at 00:38 +0100, Daniel Gustafsson wrote:
This rebase also includes a fix for pgtls_init which was sent offlist by Jacob.
The changes in pgtls_init can potentially be used to initialize the crypto
context for NSS to clean up this patch, Jacob is currently looking at that.
I'm having a hell of a time trying to get the context stuff working.
Findings so far (I have patches in progress for many of these, but it's
all blowing up because of the last problem):
NSS_INIT_NOROOTINIT is hardcoded for NSS_InitContext(), so we probably
don't need to pass it explicitly. NSS_INIT_PK11RELOAD is apparently
meant to hack around libraries that do their own PKCS loading; do we
need it?
NSS_ShutdownContext() can (and does) fail if we've leaked handles to
objects, so we need to check its return value. Once this happens,
future NSS_InitContext() calls behave poorly. Currently we leak the
pr_fd as well as a handful of server_cert handles.
NSS_NoDB_Init() is going to pin NSS in memory. For the backend this is
probably okay, but for libpq clients that's probably not what we want.
The first database loaded by NSS_InitContext() becomes the "default"
database. This is what I'm currently hung up on. I can't figure out how
to get NSS to use the database that was loaded for the current
connection, so in my local patches for the issues above, client
certificates fail to load. I can work around it temporarily for the
tests, but this will be a problem if any libpq clients load up multiple
independent databases for use with separate connections. Anyone know if
this is a supported use case for NSS?
--Jacob
On Wed, Mar 24, 2021 at 12:05:35AM +0000, Jacob Champion wrote:
The first database loaded by NSS_InitContext() becomes the "default"
database. This is what I'm currently hung up on. I can't figure out how
to get NSS to use the database that was loaded for the current
connection, so in my local patches for the issues above, client
certificates fail to load. I can work around it temporarily for the
tests, but this will be a problem if any libpq clients load up multiple
independent databases for use with separate connections. Anyone know if
this is a supported use case for NSS?
Are you referring to the case of threading here? This should be a
supported case, as threads created by an application through libpq
could perfectly use completely different connection strings.
--
Michael
On Tue, Mar 23, 2021 at 12:38:50AM +0100, Daniel Gustafsson wrote:
Thanks again for reviewing, another version which addresses the remaining
issues will be posted soon but I wanted to get this out to give further reviews
something that properly works.
I have been looking at the infrastructure of the tests, patches 0002
(some refactoring) and 0003 (more refactoring with tests for NSS), and
I am a bit confused by its state.
First, I think that the split is not completely clear. For example,
patch 0003 has changes for OpenSSL.pm and Server.pm, but wouldn't it
be better to have all the refactoring infrastructure only in 0002,
with 0003 introducing only the NSS pieces for its internal data and
NSS.pm?
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
001_ssltests.pl and 002_scram.pl have NSS-related parameters, which
does not look like a clean separation to me as there are OpenSSL tests
that use some NSS parts, and the main scripts should remain neutral in
terms setting contents, including only variables and callbacks that
should be filled specifically for each SSL implementation, no? Aren't
we missing a second piece here with a set of callbacks for the
per-library test paths then?
+ 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";
+ }
This had better be in its own callback, for example.
--
Michael
On Wed, 2021-03-24 at 09:28 +0900, Michael Paquier wrote:
On Wed, Mar 24, 2021 at 12:05:35AM +0000, Jacob Champion wrote:
I can work around it temporarily for the
tests, but this will be a problem if any libpq clients load up multiple
independent databases for use with separate connections. Anyone know if
this is a supported use case for NSS?Are you referring to the case of threading here? This should be a
supported case, as threads created by an application through libpq
could perfectly use completely different connection strings.
Right, but to clarify -- I was asking if *NSS* supports loading and
using separate certificate databases as part of its API. It seems like
the internals make it possible, but I don't see the public interfaces
to actually use those internals.
--Jacob
Greetings Jacob,
* Jacob Champion (pchampion@vmware.com) wrote:
On Wed, 2021-03-24 at 09:28 +0900, Michael Paquier wrote:
On Wed, Mar 24, 2021 at 12:05:35AM +0000, Jacob Champion wrote:
I can work around it temporarily for the
tests, but this will be a problem if any libpq clients load up multiple
independent databases for use with separate connections. Anyone know if
this is a supported use case for NSS?Are you referring to the case of threading here? This should be a
supported case, as threads created by an application through libpq
could perfectly use completely different connection strings.Right, but to clarify -- I was asking if *NSS* supports loading and
using separate certificate databases as part of its API. It seems like
the internals make it possible, but I don't see the public interfaces
to actually use those internals.
Yes, this is done using SECMOD_OpenUserDB, see:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Functions#SECMOD_OpenUserDB
also there's info here:
https://groups.google.com/g/mozilla.dev.tech.crypto/c/Xz6Emfcue0E
We should document that, as mentioned in the link above, the NSS find
functions will find certs in all the opened databases. As this would
all be under one application which is linked against libpq and passing
in different values for ssl_database for different connections, this
doesn't seem like it's really that much of an issue.
Thanks!
Stephen
On Wed, 2021-03-24 at 13:00 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
Right, but to clarify -- I was asking if *NSS* supports loading and
using separate certificate databases as part of its API. It seems like
the internals make it possible, but I don't see the public interfaces
to actually use those internals.Yes, this is done using SECMOD_OpenUserDB, see:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Functions#SECMOD_OpenUserDB
Ah, I had assumed that the DB-specific InitContext was using this
behind the scenes; apparently not. I will give that a try, thanks!
also there's info here:
https://groups.google.com/g/mozilla.dev.tech.crypto/c/Xz6Emfcue0E
We should document that, as mentioned in the link above, the NSS find
functions will find certs in all the opened databases. As this would
all be under one application which is linked against libpq and passing
in different values for ssl_database for different connections, this
doesn't seem like it's really that much of an issue.
I could see this being a problem if two client certificate nicknames
collide across multiple in-use databases, maybe?
--Jacob
Greetings,
* Jacob Champion (pchampion@vmware.com) wrote:
On Wed, 2021-03-24 at 13:00 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
Right, but to clarify -- I was asking if *NSS* supports loading and
using separate certificate databases as part of its API. It seems like
the internals make it possible, but I don't see the public interfaces
to actually use those internals.Yes, this is done using SECMOD_OpenUserDB, see:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Functions#SECMOD_OpenUserDB
Ah, I had assumed that the DB-specific InitContext was using this
behind the scenes; apparently not. I will give that a try, thanks!also there's info here:
https://groups.google.com/g/mozilla.dev.tech.crypto/c/Xz6Emfcue0E
We should document that, as mentioned in the link above, the NSS find
functions will find certs in all the opened databases. As this would
all be under one application which is linked against libpq and passing
in different values for ssl_database for different connections, this
doesn't seem like it's really that much of an issue.I could see this being a problem if two client certificate nicknames
collide across multiple in-use databases, maybe?
Right, in such a case either cert might get returned and it's possible
that the "wrong" one is returned and therefore the connection would end
up failing, assuming that they aren't actually the same and just happen
to be in both.
Seems like we could use SECMOD_OpenUserDB() and then pass the result
from that into PK11_ListCertsInSlot() and scan through the certs in just
the specified database to find the one we're looking for if we really
feel compelled to try and address this risk. I've reached out to the
NSS folks to see if they have any thoughts about the best way to address
this.
Thanks,
Stephen
On 24 Mar 2021, at 04:54, Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Mar 23, 2021 at 12:38:50AM +0100, Daniel Gustafsson wrote:
Thanks again for reviewing, another version which addresses the remaining
issues will be posted soon but I wanted to get this out to give further reviews
something that properly works.I have been looking at the infrastructure of the tests, patches 0002
(some refactoring) and 0003 (more refactoring with tests for NSS), and
I am a bit confused by its state.First, I think that the split is not completely clear. For example,
patch 0003 has changes for OpenSSL.pm and Server.pm, but wouldn't it
be better to have all the refactoring infrastructure only in 0002,
with 0003 introducing only the NSS pieces for its internal data and
NSS.pm?
Yes. Juggling a patchset of this size is errorprone. This is why I opened the
separate thread for this where the patch can be held apart cleaner, so let's
take this discussion over there. I will post an updated patch there shortly.
+ keyfile => 'server-password', + nssdatabase => 'server-cn-only.crt__server-password.key.db', + passphrase_cmd => 'echo secret1', 001_ssltests.pl and 002_scram.pl have NSS-related parameters, which does not look like a clean separation to me as there are OpenSSL tests that use some NSS parts, and the main scripts should remain neutral in terms setting contents, including only variables and callbacks that should be filled specifically for each SSL implementation, no? Aren't we missing a second piece here with a set of callbacks for the per-library test paths then?
Well, then again, keyfile is an OpenSSL specific parameter, it just happens to
be named quite neutrally. I'm not sure how to best express the certificate and
key requirements of a test since the testcase is the source of truth in terms
of what it requires. If we introduce a standard set of cert/keys which all
backends are required to supply, we could refer to those. Tests that need
something more specific can then go into 00X_<library>.pl. There is a balance
to strike though, there is a single backend now with at most one on the horizon
which is yet to be decided upon, making it too generic may end up making test
writing overcomplicated. Do you have any concretee ideas?
+ 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"; + } This had better be in its own callback, for example.
Yes, this one is a clearer case, fixed in the v2 patch which will be posted on
the separate thread.
--
Daniel Gustafsson https://vmware.com/
On Wed, 2021-03-24 at 14:10 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
I could see this being a problem if two client certificate nicknames
collide across multiple in-use databases, maybe?Right, in such a case either cert might get returned and it's possible
that the "wrong" one is returned and therefore the connection would end
up failing, assuming that they aren't actually the same and just happen
to be in both.Seems like we could use SECMOD_OpenUserDB() and then pass the result
from that into PK11_ListCertsInSlot() and scan through the certs in just
the specified database to find the one we're looking for if we really
feel compelled to try and address this risk. I've reached out to the
NSS folks to see if they have any thoughts about the best way to address
this.
Some additional findings (NSS 3.63), please correct me if I've made any mistakes:
The very first NSSInitContext created is special. If it contains a database, that database will be considered part of the "internal" slot and its certificates can be referenced directly by nickname. If it doesn't have a database, the internal slot has no certificates, and it will continue to have zero certificates until NSS is completely shut down and reinitialized with a new "first" context.
Databases that are opened *after* the first one are given their own separate slots. Any certificates that are part of those databases seemingly can't be referenced directly by nickname. They have to be prefixed by their token name -- a name which you don't have if you used NSS_InitContext() to create the database. You have to use SECMOD_OpenUserDB() instead. This explains some strange failures I was seeing in local testing, where the order of InitContext determined whether our client certificate selection succeeded or failed.
If you SECMOD_OpenUserDB() a database that is identical to the first (internal) database, NSS deduplicates for you and just returns the internal slot. Which seems like it's helpful, except you're not allowed to close that database, and you have to know not to close it by checking to see whether that slot is the "internal key slot". It appears to remain open until NSS is shut down entirely.
But if you open a database that is *not* the magic internal database,
and then open a duplicate of that one, NSS creates yet another new slot
for the duplicate. So SECMOD_OpenUserDB() may or may not be a resource
hog, depending on the global state of the process at the time libpq
opens its first connection. We won't be able to control what the parent
application will do before loading us up.
It also doesn't look like any of the SECMOD_* machinery that we're
looking at is thread-safe, but I'd really like to be wrong...
--Jacob
On 23 Mar 2021, at 20:04, Stephen Frost <sfrost@snowman.net> wrote:
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
On 22 Mar 2021, at 00:49, Stephen Frost <sfrost@snowman.net> wrote:
Thanks for the review! Below is a partial response, I haven't had time to
address all your review comments yet but I wanted to submit a rebased patchset
directly since the current version doesn't work after recent changes in the
tree. I will address the remaining comments tomorrow or the day after.Great, thanks!
This rebase also includes a fix for pgtls_init which was sent offlist by Jacob.
The changes in pgtls_init can potentially be used to initialize the crypto
context for NSS to clean up this patch, Jacob is currently looking at that.Ah, cool, sounds good.
They aren't the same and it might not be
clear what's going on if one was to somehow mix them (at least if pr_fd
continues to sometimes be a void*, but I wonder why that's being
done..? more on that later..).To paraphrase from a later in this email, there are collisions between nspr and
postgres on things like BITS_PER_BYTE, and there were also collisions on basic
types until I learned about NO_NSPR_10_SUPPORT. By moving the juggling of this
into common/nss.h we can use proper types without introducing that pollution
everywhere. I will address these places.Ah, ok, and great, that sounds good.
+ /* + * 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. + */Should we pull these error messages out into another header so that
they're in one place to make sure they're kept consistent, if we really
want to put the effort in to keep them the same..? I'm not 100% sure
that it's actually necessary to do so, but defining these in one place
would help maintain this if we want to. Also alright with just keeping
the comment, not that big of a deal.It might make sense to pull them into common/nss.h, but seeing the error
message right there when reading the code does IMO make it clearer so it's a
doubleedged sword. Not sure what is the best option, but I'm not married to
the current solution so if there is consensus to pull them out somewhere I'm
happy to do so.My thought was to put them into some common/ssl.h or something along
those lines but I don't see it as a big deal either way really. You
make a good point that having the error message there when reading the
code is nice.
Thinking more on this, I think my vote will be to keep them duplicated in the
code for readability. Unless there are strong feelings against I think we at
least should start there.
Maybe we should put a stake in the ground that says "we only support
back to version X of NSS", test with that and a few more recent versions
and the most recent, and then rip out anything that's needed for
versions which are older than that?Yes, right now there is very little in the patch which caters for old versions,
the PR_Init call might be one of the few offenders. There has been discussion
upthread about settling for a required version, combining the insights learned
there with a survey of which versions are commonly available packaged.Once we settle on a version we can confirm if PR_Init is/isn't needed and
remove all traces of it if not.I don't really see this as all that hard to do- I'd suggest we look at
what systems someone might reasonably deploy v14 on. To that end, I'd
say "only systems which are presently supported", so: RHEL7+, Debian 9+,
Ubuntu 16.04+.
Sounds reasonable.
Looking at those, I see:
Ubuntu 16.04: 3.28.4
RHEL6: v3.28.4
Debian: 3.26.2
I assume these have matching NSPR versions placing the Debian 9 NSPR package as
the lowest required version for that?
I have a pretty hard time imagining that someone is going to want to build PG
v14 w/ NSS 2.0 ...Let alone compiling 2.0 at all on a recent system..
Indeed, and given the above, it seems entirely reasonable to make the
requirement be NSS v3+, no? I wouldn't be against making that even
tighter if we thought it made sense to do so.
I think anything but doing that would be incredibly unreasonable.
Also- we don't seem to complain at all about a cipher being specified that we
don't find? Guess I would think that we might want to throw a WARNING in such
a case, but I could possibly be convinced otherwise.No, I think you're right, we should throw WARNING there or possibly even a
higher elevel. Should that be a COMMERROR even?I suppose the thought I was having was that we might want to allow some
string that covered all the OpenSSL and NSS ciphers that someone feels
comfortable with and we'd just ignore the ones that don't make sense for
the particular library we're currently built with. Making it a
COMMERROR seems like overkill and I'm not entirely sure we actually want
any warning since we might then be constantly bleating about it.
Right, with a string like that we'd induce WARNING fatigue quickly. Catching
the case of *no* ciphers enabled with a COMMERROR is going some way towards
being helpful to the user in debugging the failed connection here.
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRStatus +pg_ssl_close(PRFileDesc *fd) +{ + /* + * Disconnect our private Port from the fd before closing out the stack. + * (Debug builds of NSPR will assert if we do not.) + */ + fd->secret = NULL; + return PR_GetDefaultIOMethods()->close(fd); +}Regarding these, I find myself wondering how they're different from the
defaults..? I mean, the above just directly called
PR_GetDefaultIOMethods() to then call it's close() function- are the
fd->lower_methods->recv/send not the default methods? I don't quite get
what the point is from having our own callbacks here if they just do
exactly what the defaults would do (or are there actually no defined
defaults and you have to provide these..?).It's really just to cope with debug builds of NSPR which assert that fd->secret
is null before closing.And we have to override the recv/send functions for this too..? Sorry,
my comment wasn't just about the close() method but about the others
too.
Ah, no we can ditch the .send and .recv functions and stick with the default
built-ins, I just confirmed this and removed them. I think they are leftovers
from when I injected debug code there during development, they were as you say
copies of the default.
+ /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +}Is there never a reason someone might want the pointer returned by
NSS_InitContext? I don't know that there is but it might be something
to consider (we could even possibly have our own structure returned by
this function which includes both, maybe..?). Not sure if there's a
sensible use-case for that or not just wanted to bring it up as it's
something I asked myself while reading through this patch.Not sure I understand what you're asking for here, did you mean "is there ever
a reason"?Eh, poor wording on my part. You're right, the question, reworded
again, was "Would someone want to get the context returned by
NSS_InitContext?". If we think there's a reason that someone might want
that context then perhaps we should allow getting it, in addition to the
pr_fd. If there's really no reason to ever want the context from
NSS_InitContext then what you have here where we're returning pr_fd is
probably fine.
I can't think of any reason, maybe Jacob who has been knee-deep in NSS contexts
have insights which tell a different story?
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index c601071838..7f10da3010 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -448,6 +448,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) } #endif /* USE_OPENSSL */+#ifndef USE_NSS + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return NULL; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + return; +} + +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + return NULL; +} +#endif /* USE_NSS */Isn't this '!USE_NSS'?
Technically it is, but using just /* USE_NSS */ is consistent with the rest of
blocks in the file.Hrmpf. I guess it seems a bit confusing to me to have to go find the
opening #ifndef to realize that it's actally !USE_NSS.. In other words,
I would think we'd actually want to fix all of these, heh. I only
actually see one case on a quick grep where it's wrong for USE_OPENSSL
and so that doesn't seem like it's really a precedent and is more of a
bug. We certainly say 'not OPENSSL' in one place today too and also
have a number of places where we have: #endif ... /* ! WHATEVER */.
No disagreement from me. To cut down the size of this patchset however I
propose that we tackle this separately and leave this as is in this thread
since it's in line with the rest of the file (for now).
Subject: [PATCH v30 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/<lib>
modules for the implementation details.No changes are done to the actual tests, this only change how setup and
teardown is performed.Presumably this could be committed ahead of the main NSS support?
Correct, I think this has merits even if NSS support is ultimately rejected.
Ok- could you break it out on to its own thread and I'll see about
committing it soonish, to get it out of the way?
It was already on it's own thread, as we discussed offlist. I have since
rebased and expanded that patch over in that thread which has gotten review
that needs to be addressed. As such, I will not update that patch in the
series in this thread but keep the changes on that thread, and then pull them
back into here when ready.
Subject: [PATCH v30 5/9] nss: Documentation +++ b/doc/src/sgml/acronyms.sgml @@ -684,6 +717,16 @@ </listitem> </varlistentry>+ <varlistentry> + <term><acronym>TLS</acronym></term> + <listitem> + <para> + <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security"> + Transport Layer Security</ulink> + </para> + </listitem> + </varlistentry>We don't have this already..? Surely we should..
We really should, especially since we've had <acronym>TLS</acronym> in
config.sgml since 2014 (c6763156589). That's another small piece that could be
committed on it's own to cut down the size of this patchset (even if only by a
tiny amount).Ditto on this. :)
Done in /messages/by-id/27109504-82DB-41A8-8E63-C0498314F5B0@yesql.se
@@ -1288,7 +1305,9 @@ include_dir 'conf.d' connections using TLS version 1.2 and lower are affected. There is currently no setting that controls the cipher choices used by TLS version 1.3 connections. The default value is - <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a + <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have + been built with <productname>OpenSSL</productname> as the + <acronym>SSL</acronym> library. The default is usually a reasonable choice unless you have specific security requirements. </para>Shouldn't we say something here wrt NSS?
We should, but I'm not entirely what just yet. Need to revisit that.
Not sure if we really want to do this but at least with ssllabs.com,
postgresql.org gets an 'A' rating with this set:ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA256
ECDHE-RSA-AES128-SHA256
ECDHE-ECDSA-AES128-SHA
ECDHE-RSA-AES256-SHA384
ECDHE-RSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA384
ECDHE-ECDSA-AES256-SHA
ECDHE-RSA-AES256-SHA
DHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA
ECDHE-ECDSA-DES-CBC3-SHA
ECDHE-RSA-DES-CBC3-SHA
EDH-RSA-DES-CBC3-SHA
AES128-GCM-SHA256
AES256-GCM-SHA384
AES128-SHA256
AES256-SHA256
AES128-SHA
AES256-SHA
DES-CBC3-SHA
!DSSWhich also seems kinda close to what the default when built with OpenSSL
ends up being? Thought the ssllabs report does list which ones it
thinks are weak and so we might consider excluding those by default too:
Agreed, maintaining parity (or thereabouts) with OpenSSL defaults taking
industry best practices into account is probably what we should aim for.
@@ -1490,8 +1509,11 @@ include_dir 'conf.d' <para> Sets an external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained. By - default, this parameter is empty, which means the built-in prompting - mechanism is used. + default, this parameter is empty. When the server is using + <productname>OpenSSL</productname>, this means the built-in prompting + mechanism is used. When using <productname>NSS</productname>, there is + no default prompting so a blank callback will be used returning an + empty password. </para>Maybe we should point out here that this requires the database to not
require a password..? So if they have one, they need to set this, or
maybe we should provide a default one..I've added a sentence on not using a password for the cert database. I'm not
sure if providing a default one is a good idea but it's no less insecure than
having no password really..I was meaning a default callback to prompt, not sure if that was clear.
Ah, no that's not what I thought you meant. Do you have any thoughts on what
that callback would look like? Take a password on a TTY input?
Below are a few fixes addressed from the original review email:
+ /* + * Set up the custom IO layer. + */Might be good to mention that the IO Layer is what sets up the
read/write callbacks to be used.Good point, will do in the next version of the patchset.
Fixed.
+ port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + { + ereport(COMMERROR, + (errmsg("unable to initialize"))); + return -1; + }Maybe a comment and a better error message for this?
Will do.
Fixed.
+ PR_Close(model);
This might deserve one also, the whole 'model' construct is a bit
different. :)Agreed. will do.
Fixed.
Also, I get that they do similar jobs and that one is in the frontend
and the other is in the backend, but I'm not a fan of having two
'ssl_protocol_version_to_nss()'s functions that take different argument
types but have exact same name and do functionally different things..Good point, I'll change that.
Fixed.
+ /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure cipher policy: %s"), + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + }Probably good to pull over at least some parts of the comments made in
the backend code about SetDomesticPolicy() actually enabling everything
(just like all the policies apparently do)...Good point, will do.
Fixed.
+int +be_tls_open_server(Port *port) +{ + SECStatus status; + PRFileDesc *model; + PRFileDesc *pr_fd;pr_fd here is materially different from port->pr_fd, no? As in, one is
the NSS raw TCP fd while the other is the SSL fd, right? Maybe we
should use two different variable names to try and make sure they don't
get confused? Might even set this to NULL after we are done with it
too.. Then again, I see later on that when we do the dance with the
'model' PRFileDesc that we just use the same variable- maybe we should
do that? That is, just get rid of this 'pr_fd' and use port->pr_fd
always?Hmm, I think you're right. I will try that for the next patchset version.
Similar comments to the backend code- should we just always use
conn->pr_fd? Or should we rename pr_fd to something else?Renaming is probably not a bad idea, will fix.
Both fixed.
Additionally, a few other off-list reported issues are also fixed in this
version (such as fixing the silly markup doc error and testplan off-by-one etc).
--
Daniel Gustafsson https://vmware.com/
Attachments:
v32-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v32-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 580cd53179d409801e9407196640522d8008e2fc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v32 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 6 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 39 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 408 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 06ad9aeb71..7d070a758b 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 92193f35fb..6296d32ff6 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..57d44080b4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,18 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 5e2255a2f5..c50ea54530 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -325,6 +325,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -337,6 +343,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -914,6 +923,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 2aca882a2b..ed8aa00bcb 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -55,6 +55,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index bc65185130..cca48b3beb 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -134,6 +134,11 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -198,12 +203,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -261,12 +273,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -434,9 +453,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -466,6 +490,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 710f26f8ab..e99550ff45 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -300,10 +300,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -493,6 +496,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -545,6 +549,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1003,6 +1014,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
v32-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v32-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From faf423f445db21c0d3767479b8f71da92914a494 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v32 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v32-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v32-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From b1af96493fa6d14a5dbfe8595417f9935607d9ff Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v32 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v32-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v32-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 570823e75226a8e4866f4595377db7fbb8ece630 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v32 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 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
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.21.1 (Apple Git-122.3)
v32-0005-nss-Documentation.patchapplication/octet-stream; name=v32-0005-nss-Documentation.patch; x-unix-mode=0644Download
From a4362886cce63a0141ed891148795b2928d0c52f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v32 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 | 43 +++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 337 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 473 insertions(+), 92 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 4e5ec983c0..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
@@ -684,6 +717,16 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>TLS</acronym></term>
+ <listitem>
+ <para>
+ <ulink url="https://en.wikipedia.org/wiki/Transport_Layer_Security">
+ Transport Layer Security</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>TOAST</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ddc6d789d8..6d003a3139 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1306,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1510,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index be674fbaa9..b1640c4aed 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1808,8 +1969,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1824,8 +1985,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2587,6 +2748,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2610,9 +2773,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2639,6 +2808,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8560,6 +8733,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8586,6 +8764,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..bb13f81326 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v32-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v32-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 94039e0e5545facb63d6ba79721eaac326c6b08d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v32 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.21.1 (Apple Git-122.3)
v32-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v32-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 83ff709c7e1968d00c8cd01addd4563b835d7cfb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v32 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 | 342 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 582 insertions(+), 163 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 4b53fdf6c0..8d4afa9dd0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -98,6 +262,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -130,19 +302,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -168,14 +361,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 6124d9ea23..34bdb574f9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 104;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 104;
}
else
{
- plan tests => 100;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,70 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ test_connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ test_connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test",
+ "sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +151,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -111,95 +171,118 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db",
+ qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -212,14 +295,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,20 +320,20 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -260,21 +343,21 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -287,14 +370,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -303,11 +386,11 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -315,16 +398,16 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir cert_database=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -345,32 +428,50 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ test_connect_ok(
+ $common_connstr,
+ "sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -386,32 +487,43 @@ test_connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
test_connect_ok(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-test_connect_ok(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
@@ -451,18 +563,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -475,10 +588,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -490,7 +606,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -514,24 +630,28 @@ test_connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db";
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
test_connect_fails(
$common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 3383b3e531..801c945e88 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -97,7 +110,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
test_connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db",
"dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index e59554f85b..da25645d25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -202,46 +201,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v32-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v32-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From ba07afc9a2f0978bf5165ab6829690a9540a1710 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v32 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index bfada03d3e..6124d9ea23 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -574,7 +536,5 @@ test_connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 410b9e910d..3383b3e531 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ test_connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..e59554f85b 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
test_connect_fails
test_connect_ok
@@ -144,14 +164,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -159,20 +186,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -182,13 +225,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v32-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v32-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From e0768abdd748e1503b7c2e90530205059adee9a8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v32 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
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1411 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 +++
src/include/common/nss.h | 49 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1105 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
15 files changed, 2857 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..3d51a7c9b8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8946,7 +8946,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 994251e7d9..0512d058e8 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2801,7 +2801,13 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..97bca56e95
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1411 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+static 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");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index bb603ad209..c10b6dd204 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0c5dc4d3e8..b1181ca7aa 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4334,7 +4334,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4402,6 +4406,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4430,8 +4444,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..a361dd1f68
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 891394b0c3..636aa71324 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -205,6 +205,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -287,7 +291,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -297,6 +301,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b20deeb555..e2eb64494f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -87,6 +87,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 53b354abb2..56129a0179 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -341,6 +341,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* 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..b2b8906715
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1105 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = 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 = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ conn->nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->cert_database &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %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 = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database &&
+ strlen(conn->cert_database) > 0 &&
+ cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * 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") == 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) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * cert_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
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index cee42d4843..fd5a0e99a3 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -643,6 +643,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6374ec657a..65391d9a92 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 */
+#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 *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -522,6 +527,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -783,7 +810,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
On Fri, 2021-03-26 at 00:22 +0100, Daniel Gustafsson wrote:
On 23 Mar 2021, at 20:04, Stephen Frost <sfrost@snowman.net> wrote:
Eh, poor wording on my part. You're right, the question, reworded
again, was "Would someone want to get the context returned by
NSS_InitContext?". If we think there's a reason that someone might want
that context then perhaps we should allow getting it, in addition to the
pr_fd. If there's really no reason to ever want the context from
NSS_InitContext then what you have here where we're returning pr_fd is
probably fine.I can't think of any reason, maybe Jacob who has been knee-deep in NSS contexts
have insights which tell a different story?
The only thing you can do with a context pointer is shut it down, and I
don't think that's something that should be exposed.
--Jacob
Greetings,
* Jacob Champion (pchampion@vmware.com) wrote:
On Wed, 2021-03-24 at 14:10 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
I could see this being a problem if two client certificate nicknames
collide across multiple in-use databases, maybe?Right, in such a case either cert might get returned and it's possible
that the "wrong" one is returned and therefore the connection would end
up failing, assuming that they aren't actually the same and just happen
to be in both.Seems like we could use SECMOD_OpenUserDB() and then pass the result
from that into PK11_ListCertsInSlot() and scan through the certs in just
the specified database to find the one we're looking for if we really
feel compelled to try and address this risk. I've reached out to the
NSS folks to see if they have any thoughts about the best way to address
this.Some additional findings (NSS 3.63), please correct me if I've made any mistakes:
The very first NSSInitContext created is special. If it contains a database, that database will be considered part of the "internal" slot and its certificates can be referenced directly by nickname. If it doesn't have a database, the internal slot has no certificates, and it will continue to have zero certificates until NSS is completely shut down and reinitialized with a new "first" context.
Databases that are opened *after* the first one are given their own separate slots. Any certificates that are part of those databases seemingly can't be referenced directly by nickname. They have to be prefixed by their token name -- a name which you don't have if you used NSS_InitContext() to create the database. You have to use SECMOD_OpenUserDB() instead. This explains some strange failures I was seeing in local testing, where the order of InitContext determined whether our client certificate selection succeeded or failed.
This is more-or-less what we would want though, right..? If a user asks
for a connection with ssl_database=blah and sslcert=whatever, we'd want
to open database 'blah' and search (just) that database for cert
'whatever'. We could possibly offer other options in the future but
certainly this would work and be the most straight-forward and expected
behavior.
If you SECMOD_OpenUserDB() a database that is identical to the first (internal) database, NSS deduplicates for you and just returns the internal slot. Which seems like it's helpful, except you're not allowed to close that database, and you have to know not to close it by checking to see whether that slot is the "internal key slot". It appears to remain open until NSS is shut down entirely.
Seems like we shouldn't do that and should just use SECMOD_OpenUserDB()
for opening databases.
But if you open a database that is *not* the magic internal database,
and then open a duplicate of that one, NSS creates yet another new slot
for the duplicate. So SECMOD_OpenUserDB() may or may not be a resource
hog, depending on the global state of the process at the time libpq
opens its first connection. We won't be able to control what the parent
application will do before loading us up.
I would think we'd want to avoid re-opening the same database multiple
times, to avoid the duplicate slots and such. If the application code
does it themselves, well, there's not much we can do about that, but we
could at least avoid doing so in *our* code. I wouldn't expect us to be
opening hundreds of databases either and so keeping a simple list around
of what we've opened and scanning it seems like it'd be workable. Of
course, this could likely be improved in the future but I would think
that'd be good for an initial implementation.
We could also just generally caution users in our documentation against
using multiple databases. The NSS folks discourage doing so and it
doesn't strike me as being a terribly useful thing to do anyway, at
least from within one invocation of an application. Still, if we could
make it work reasonably well, then I'd say we should go ahead and do so.
It also doesn't look like any of the SECMOD_* machinery that we're
looking at is thread-safe, but I'd really like to be wrong...
That's unfortuante but solvable by using our own locks, similar
to what's done in fe-secure-openssl.c.
Thanks!
Stephen
On Fri, 2021-03-26 at 15:33 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
Databases that are opened *after* the first one are given their own
separate slots. [...]This is more-or-less what we would want though, right..? If a user asks
for a connection with ssl_database=blah and sslcert=whatever, we'd want
to open database 'blah' and search (just) that database for cert
'whatever'. We could possibly offer other options in the future but
certainly this would work and be the most straight-forward and expected
behavior.
Yes, but see below.
If you SECMOD_OpenUserDB() a database that is identical to the first
(internal) database, NSS deduplicates for you and just returns the
internal slot. Which seems like it's helpful, except you're not
allowed to close that database, and you have to know not to close it
by checking to see whether that slot is the "internal key slot". It
appears to remain open until NSS is shut down entirely.Seems like we shouldn't do that and should just use SECMOD_OpenUserDB()
for opening databases.
We don't have control over whether or not this happens. If the
application embedding libpq has already loaded the database into the
internal slot via its own NSS initialization, then when we call
SECMOD_OpenUserDB() for that same database, the internal slot will be
returned and we have to handle it accordingly.
It's not a huge amount of work, but it is magic knowledge that has to
be maintained, especially in the absence of specialized clientside
tests.
But if you open a database that is *not* the magic internal database,
and then open a duplicate of that one, NSS creates yet another new slot
for the duplicate. So SECMOD_OpenUserDB() may or may not be a resource
hog, depending on the global state of the process at the time libpq
opens its first connection. We won't be able to control what the parent
application will do before loading us up.I would think we'd want to avoid re-opening the same database multiple
times, to avoid the duplicate slots and such. If the application code
does it themselves, well, there's not much we can do about that, but we
could at least avoid doing so in *our* code. I wouldn't expect us to be
opening hundreds of databases either and so keeping a simple list around
of what we've opened and scanning it seems like it'd be workable. Of
course, this could likely be improved in the future but I would think
that'd be good for an initial implementation.[...]
It also doesn't look like any of the SECMOD_* machinery that we're
looking at is thread-safe, but I'd really like to be wrong...That's unfortuante but solvable by using our own locks, similar
to what's done in fe-secure-openssl.c.
Yeah. I was hoping to avoid implementing our own locks and refcounts,
but it seems like it's going to be required.
--Jacob
Greetings,
* Jacob Champion (pchampion@vmware.com) wrote:
On Fri, 2021-03-26 at 15:33 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
Databases that are opened *after* the first one are given their own
separate slots. [...]This is more-or-less what we would want though, right..? If a user asks
for a connection with ssl_database=blah and sslcert=whatever, we'd want
to open database 'blah' and search (just) that database for cert
'whatever'. We could possibly offer other options in the future but
certainly this would work and be the most straight-forward and expected
behavior.Yes, but see below.
If you SECMOD_OpenUserDB() a database that is identical to the first
(internal) database, NSS deduplicates for you and just returns the
internal slot. Which seems like it's helpful, except you're not
allowed to close that database, and you have to know not to close it
by checking to see whether that slot is the "internal key slot". It
appears to remain open until NSS is shut down entirely.Seems like we shouldn't do that and should just use SECMOD_OpenUserDB()
for opening databases.We don't have control over whether or not this happens. If the
application embedding libpq has already loaded the database into the
internal slot via its own NSS initialization, then when we call
SECMOD_OpenUserDB() for that same database, the internal slot will be
returned and we have to handle it accordingly.It's not a huge amount of work, but it is magic knowledge that has to
be maintained, especially in the absence of specialized clientside
tests.
Ah.. yeah, fair enough. We could document that we discourage
applications from doing so, but I agree that we'll need to deal with it
since it could happen.
But if you open a database that is *not* the magic internal database,
and then open a duplicate of that one, NSS creates yet another new slot
for the duplicate. So SECMOD_OpenUserDB() may or may not be a resource
hog, depending on the global state of the process at the time libpq
opens its first connection. We won't be able to control what the parent
application will do before loading us up.I would think we'd want to avoid re-opening the same database multiple
times, to avoid the duplicate slots and such. If the application code
does it themselves, well, there's not much we can do about that, but we
could at least avoid doing so in *our* code. I wouldn't expect us to be
opening hundreds of databases either and so keeping a simple list around
of what we've opened and scanning it seems like it'd be workable. Of
course, this could likely be improved in the future but I would think
that'd be good for an initial implementation.[...]
It also doesn't look like any of the SECMOD_* machinery that we're
looking at is thread-safe, but I'd really like to be wrong...That's unfortuante but solvable by using our own locks, similar
to what's done in fe-secure-openssl.c.Yeah. I was hoping to avoid implementing our own locks and refcounts,
but it seems like it's going to be required.
Yeah, afraid so.
Thanks!
Stephen
On Fri, 2021-03-26 at 18:05 -0400, Stephen Frost wrote:
* Jacob Champion (pchampion@vmware.com) wrote:
Yeah. I was hoping to avoid implementing our own locks and refcounts,
but it seems like it's going to be required.Yeah, afraid so.
I think it gets worse, after having debugged some confusing crashes.
There's already been a discussion on PR_Init upthread a bit:
Once we settle on a version we can confirm if PR_Init is/isn't needed and
remove all traces of it if not.
What the NSPR documentation omits is that implicit initialization is
not threadsafe. So NSS_InitContext() is technically "threadsafe"
because it's built on PR_CallOnce(), but if you haven't called
PR_Init() yet, multiple simultaneous PR_CallOnce() calls can crash into
each other.
So, fine. We just add our own locks around NSS_InitContext() (or around
a single call to PR_Init()). Well, the first thread to win and
successfully initialize NSPR gets marked as the "primordial" thread
using thread-local state. And it gets a pthread destructor that does...
something. So lazy initialization seems a bit dangerous regardless of
whether or not we add locks, but I can't really prove whether it's
dangerous or not in practice.
I do know that only the primordial thread is allowed to call
PR_Cleanup(), and of course we wouldn't be able to control which thread
does what for libpq clients. I don't know what other assumptions are
made about the primordial thread, or if there are any platform-specific
behaviors with older versions of NSPR that we'd need to worry about. It
used to be that the primordial thread was not allowed to exit before
any other threads, but that restriction was lifted at some point [1]https://bugzilla.mozilla.org/show_bug.cgi?id=294955.
I think we're going to need some analogue to PQinitOpenSSL() to help
client applications cut through the mess, but I'm not sure what it
should look like, or how we would maintain any sort of API
compatibility between the two flavors. And does libpq already have some
notion of a "main thread" that I'm missing?
--Jacob
On Wed, Mar 31, 2021 at 10:15:15PM +0000, Jacob Champion wrote:
I think we're going to need some analogue to PQinitOpenSSL() to help
client applications cut through the mess, but I'm not sure what it
should look like, or how we would maintain any sort of API
compatibility between the two flavors. And does libpq already have some
notion of a "main thread" that I'm missing?
Nope as far as I recall. With OpenSSL, the initialization of the SSL
mutex lock and the crypto callback initialization is done by the first
thread in.
--
Michael
Greetings,
* Michael Paquier (michael@paquier.xyz) wrote:
On Wed, Mar 31, 2021 at 10:15:15PM +0000, Jacob Champion wrote:
I think we're going to need some analogue to PQinitOpenSSL() to help
client applications cut through the mess, but I'm not sure what it
should look like, or how we would maintain any sort of API
compatibility between the two flavors. And does libpq already have some
notion of a "main thread" that I'm missing?Nope as far as I recall. With OpenSSL, the initialization of the SSL
mutex lock and the crypto callback initialization is done by the first
thread in.
Yeah, we haven't got any such concept in libpq. I do think that some of
this can simply be documented as "if you do this, then you need to make
sure to do this".
Thanks,
Stephen
On 23 Mar 2021, at 00:38, Daniel Gustafsson <daniel@yesql.se> wrote:
On 22 Mar 2021, at 00:49, Stephen Frost <sfrost@snowman.net> wrote:
Attached is a rebase on top of the recent SSL related commits with a few more
fixes from previous reviews.
+++ b/src/interfaces/libpq/fe-connect.c @@ -359,6 +359,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */ offsetof(struct pg_conn, target_session_attrs)},+ {"cert_database", NULL, NULL, NULL, + "CertificateDatabase", "", 64, + offsetof(struct pg_conn, cert_database)},I mean, maybe nitpicking here, but all the other SSL stuff is
'sslsomething' and the backend version of this is 'ssl_database', so
wouldn't it be more consistent to have this be 'ssldatabase'?Thats a good point, I was clearly Stockholm syndromed since I hadn't reflected
on that but it's clearly wrong. Will fix.
Fixed
+ /* + * 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->cert_database && conn->sslmode[0] == 'v') + { + status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\""); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), + ca_trust_name, + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + }Maybe have something a bit more here about "maybe you should specifify a
cert_database" or such?Good point, will expand with more detail.
Fixed.
+ /* + * Specify which hostname we are expecting to talk to. This is required, + * albeit mostly applies to when opening a connection to a traditional + * http server it seems. + */ + SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);We should probably also set SNI, if available (NSS 3.12.6 it seems?),
since it looks like that's going to be added to the OpenSSL code.Good point, will do.
Actually, it turns out that NSS 3.12.6 introduced the serverside SNI handling
by providing callbacks to respond to hostname verification. There was no
mention of clientside SNI in the NSS documentation that I could find, reading
the code however SSL_SetURL does actually set the SNI extension in the
ClientHello. So, clientsidee SNI (which is what is proposed for the OpenSSL
backend) is already in.
+ do + { + status = SSL_ForceHandshake(conn->pr_fd); + } + while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);We don't seem to have this loop in the backend code.. Is there some
reason that we don't? Is it possible that we need to have a loop here
too? I recall in the GSS encryption code there were definitely things
during setup that had to be looped back over on both sides to make sure
everything was finished ...Off the cuff I can't remember, will look into it.
Thinking more about this, I don't think we should have the loop at all in the
frontend either. The reason it was added was to cover cases where we're
confused about blocking but I can't actually see the case I was worried about
in the code so I think it's useless. Removed.
+ if (strcmp(attribute_name, "protocol") == 0) + { + switch (channel.protocolVersion) + { +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + case SSL_LIBRARY_VERSION_TLS_1_3: + return "TLSv1.3"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + case SSL_LIBRARY_VERSION_TLS_1_2: + return "TLSv1.2"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + case SSL_LIBRARY_VERSION_TLS_1_1: + return "TLSv1.1"; +#endif + case SSL_LIBRARY_VERSION_TLS_1_0: + return "TLSv1.0"; + default: + return "unknown"; + } + }Not sure that it really matters, but this seems like it might be useful
to have as its own function... Maybe even a data structure that both
functions use just in oppostie directions. Really minor tho. :)I suppose that wouldn't be a bad thing, will fix.
Moved this into a shared function as it's used by both frontend and backend.
It's moved mostly verbatim as it seemed simple enough to not warrant much
complication.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v33-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v33-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From b57cba2d7b9ca736a8082b551ddc4fec29503f45 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v33 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v33-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v33-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From b69256714787eab356ddfaf418acd0f471442f59 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v33 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v33-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v33-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 4fb29d99844a69c8aac711027fe92e4afd7fe8bd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v33 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 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
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.21.1 (Apple Git-122.3)
v33-0005-nss-Documentation.patchapplication/octet-stream; name=v33-0005-nss-Documentation.patch; x-unix-mode=0644Download
From 2cf96e13e896c71b335eb4c556c62fc01772f9d6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v33 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 | 337 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 463 insertions(+), 92 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 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d1e2e8c4c3..7f2bd30bca 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1272,6 +1272,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1288,7 +1306,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1490,8 +1510,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 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);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1808,8 +1969,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1824,8 +1985,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2587,6 +2748,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2610,9 +2773,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2639,6 +2808,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8626,6 +8804,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bf877c0e0c..bb13f81326 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v33-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v33-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From c48cac23709406e18f6623abc3dccdc81a5e9b6c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v33 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.21.1 (Apple Git-122.3)
v33-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v33-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 2ac09d08ed78fe2ff84491fe30313b727ceb24b7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v33 3/9] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 245 ++++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 346 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 579 insertions(+), 171 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a9eca9e049..0d6d237e7c 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -91,6 +234,7 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+
# Client certificate with multi-parth DN, signed by the client CA:
ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl req -new -key ssl/client-dn.key -out ssl/client-dn.csr -config client-dn.config
@@ -98,6 +242,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +270,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +310,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +369,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 69f4bbc61b..305758b186 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 107;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 107;
}
else
{
- plan tests => 103;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +149,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -109,88 +167,104 @@ $node->connect_ok(
"connect without server root cert sslmode=require");
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
qr/SSL error/,
"connect with wrong server root cert sslmode=require");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
qr/SSL error/,
"connect with wrong server root cert sslmode=verify-ca");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
qr/SSL error/,
"connect with wrong server root cert sslmode=verify-full");
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/,
- "connect with server CA cert, without root CA");
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -199,14 +273,14 @@ $node->connect_ok(
"mismatch between host name and server certificate sslmode=verify-ca");
$node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -219,19 +293,19 @@ $node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -239,20 +313,20 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -260,40 +334,39 @@ $node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 2");
$node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
- $common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ "$common_connstr sslmode=verify-full host=common-name.pg-ssltest.test",
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl ssldatabase=ssl/nss/root+server_ca.crt__server.crl.db",
qr/SSL error/,
"does not connect with client-side CRL file");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
qr/SSL error/,
"does not connect with client-side CRL directory");
@@ -313,29 +386,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -349,54 +439,67 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping");
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping");
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN regex mapping");
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -432,18 +535,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -454,10 +558,13 @@ SKIP:
# client cert belonging to another user
$node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
@@ -468,7 +575,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -489,23 +596,24 @@ $node->connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
$node->connect_fails(
- $common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ "$common_connstr sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
qr/SSL error/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a2993b8f22..7b216452cd 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -92,7 +105,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 19ccdd95f9..348e48af25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -167,46 +166,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v33-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v33-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 2ef17118e4fa304bb69064f343ba15d1e8e1c5b7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v33 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1a63f279c..69f4bbc61b 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -548,7 +510,5 @@ $node->connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 e31650b931..a2993b8f22 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -98,5 +98,3 @@ $node->connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 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.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -109,14 +129,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -124,20 +151,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -147,13 +190,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v33-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v33-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From ba57f00f9ee15469b7c73c955248ef40a66efae2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v33 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
---
.../postgres_fdw/expected/postgres_fdw.out | 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/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/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 +-
16 files changed, 2868 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index eff7b04f11..695784c9a6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8908,7 +8908,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 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 = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..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 <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 130374789e..9ca9838791 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4344,7 +4344,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4412,6 +4416,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4440,8 +4454,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 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;
#ifdef USE_SSL
@@ -288,7 +292,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -298,6 +302,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b20deeb555..e2eb64494f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -87,6 +87,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.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 PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = 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 = sizeof(params);
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = 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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 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 PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 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 */
+#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 >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -784,7 +811,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
v33-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v33-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 55c2d64086886c547476544a2f984ae003e92cfb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v33 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 7 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 410 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 06ad9aeb71..7d070a758b 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 92193f35fb..6296d32ff6 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 5422579a6a..cece35d26e 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,12 +85,19 @@ OBJS_COMMON += \
protocol_openssl.o \
cryptohash_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
md5.o \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 5e2255a2f5..c50ea54530 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -325,6 +325,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -337,6 +343,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -914,6 +923,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index bc65185130..703d8e54bd 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -134,6 +134,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'cryptohash_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');
@@ -198,12 +204,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -261,12 +274,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -434,9 +454,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -466,6 +491,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 710f26f8ab..e99550ff45 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -300,10 +300,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -493,6 +496,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -545,6 +549,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1003,6 +1014,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.21.1 (Apple Git-122.3)
Another rebase to cope with recent changes (hmac, ssl tests etc) that
conflicted and broke this patchset.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v34-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v34-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 0d6f2c94e5c01a4075082b9a13088648d75a8b8f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v34 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 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=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 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
+/* 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
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 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');
# 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);
# 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';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -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 => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -495,6 +498,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -549,6 +553,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -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');
--
2.21.1 (Apple Git-122.3)
v34-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v34-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From ddba6dad74d39a9ca274adf1619039e6ea471483 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v34 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.21.1 (Apple Git-122.3)
v34-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v34-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 56075d6b64edd8d8f5f7ebe96a16c3b1544d6ed1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v34 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.21.1 (Apple Git-122.3)
v34-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v34-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 7682dcaa04faa0f2f95039ced454478d29e69042 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v34 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 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
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.21.1 (Apple Git-122.3)
v34-0005-nss-Documentation.patchapplication/octet-stream; name=v34-0005-nss-Documentation.patch; x-unix-mode=0644Download
From 5c6cc108f43b1c030854773f1860f22cc440d783 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v34 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 | 337 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 463 insertions(+), 92 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 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0c9128a55d..6327c20faa 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1309,6 +1309,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -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
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1527,8 +1547,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 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);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1808,8 +1969,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1824,8 +1985,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2587,6 +2748,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2610,9 +2773,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2639,6 +2808,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8626,6 +8804,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.21.1 (Apple Git-122.3)
v34-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v34-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 9dd7ad3af4a5c1f8e87abc15e786d2ce2e346984 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v34 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.21.1 (Apple Git-122.3)
v34-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v34-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From b2af919a3a76cf744511be88d71e17806ca61659 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v34 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 | 359 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 31 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +---
6 files changed, 585 insertions(+), 177 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 := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -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 PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +269,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +309,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +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/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index bd1b1b42c7..f309b26086 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -9,13 +9,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 107;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 107;
}
else
{
- plan tests => 103;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +149,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -109,88 +167,105 @@ $node->connect_ok(
"connect without server root cert sslmode=require");
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-ca");
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error: certificate verify failed/,
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
+ qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/,
"connect with wrong server root cert sslmode=require");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error: certificate verify failed/,
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
+ qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/,
"connect with wrong server root cert sslmode=verify-ca");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error: certificate verify failed/,
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
+ qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/,
"connect with wrong server root cert sslmode=verify-full");
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error: certificate verify failed/,
- "connect with server CA cert, without root CA");
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error: certificate verify failed/,
+ "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error: certificate verify failed/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error: certificate verify failed/,
+ "CRL belonging to a different CA");
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- qr/SSL error: certificate verify failed/,
- "directory CRL belonging to a different CA");
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ qr/SSL error: certificate verify failed/,
+ "directory CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -199,14 +274,14 @@ $node->connect_ok(
"mismatch between host name and server certificate sslmode=verify-ca");
$node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/,
"mismatch between host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -219,19 +294,19 @@ $node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names");
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -239,20 +314,20 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name");
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"host name not matching with a single X.509 Subject Alternative Name wildcard"
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -260,41 +335,40 @@ $node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 2");
$node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
"certificate with both a CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
- $common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ "$common_connstr sslmode=verify-full host=common-name.pg-ssltest.test",
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./,
"server certificate without CN or SANs sslmode=verify-full");
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error: certificate verify failed/,
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl ssldatabase=ssl/nss/root+server_ca.crt__server.crl.db",
+ qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/,
"does not connect with client-side CRL file");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
- qr/SSL error: certificate verify failed/,
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
+ qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/,
"does not connect with client-side CRL directory");
# pg_stat_ssl
@@ -313,29 +387,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid SSL protocol version range/,
"connection failure with incorrect range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_min_protocol_version value/,
"connection failure with an incorrect SSL protocol minimum bound");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
qr/invalid ssl_max_protocol_version value/,
"connection failure with an incorrect SSL protocol maximum bound");
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -349,54 +440,67 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
);
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping");
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping");
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN regex mapping");
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -432,18 +536,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -454,21 +559,24 @@ SKIP:
# client cert belonging to another user
$node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
"certificate authorization fails with client cert belonging to another user"
);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error: sslv3 alert certificate revoked/,
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
"certificate authorization fails with revoked client cert");
# 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 =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -489,24 +597,25 @@ $node->connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error: tlsv1 alert unknown ca/, "intermediate client certificate is missing");
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/,
+ "intermediate client certificate is missing");
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error: sslv3 alert certificate revoked/,
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Encountered end of file/,
"certificate authorization fails with revoked client cert with server-side CRL directory");
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a2993b8f22..7b216452cd 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -13,7 +13,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -92,7 +105,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
qr/channel binding required, but server authenticated client without channel binding/,
"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 19ccdd95f9..348e48af25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -167,46 +166,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v34-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v34-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From baba6b4e423730893f8dfaa7cb72f7528c6f64b6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v34 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 49af9c9a07..bd1b1b42c7 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -548,7 +510,5 @@ $node->connect_fails(
"certificate authorization fails with revoked client cert with server-side CRL directory");
# 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 e31650b931..a2993b8f22 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -98,5 +98,3 @@ $node->connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 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.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -109,14 +129,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -124,20 +151,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -147,13 +190,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.21.1 (Apple Git-122.3)
v34-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v34-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 6a423acda423123077952973945e78e25b6d796f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v34 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
---
.../postgres_fdw/expected/postgres_fdw.out | 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/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/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 +-
16 files changed, 2868 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 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_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 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 = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..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 <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 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[] =
},
&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[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4466,8 +4480,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 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;
#ifdef USE_SSL
@@ -288,7 +292,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -298,6 +302,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 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_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.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 PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = 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 = sizeof(params);
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = 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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 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 PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 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 */
+#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 >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -784,7 +811,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.21.1 (Apple Git-122.3)
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
Attachments:
v35-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchtext/x-diff; charset=us-asciiDownload
From 5db9fbc12897b28d216a1480cd529f0158525a37 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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 undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 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;
#ifdef USE_SSL
@@ -288,7 +292,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -298,6 +302,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 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_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 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
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 = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..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 <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found. */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 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[] =
},
&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[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4466,8 +4480,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.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 PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..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 <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = 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 = sizeof(params);
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = 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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * 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 requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 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 PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 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 */
+#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 >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -784,7 +811,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_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_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
--
2.31.0
v35-0002-Refactor-SSL-testharness-for-multiple-library.patchtext/x-diff; charset=us-asciiDownload
From de8baa6ebe0978c097303bcd04efd0a2a82f2474 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 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;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -567,7 +529,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 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;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -104,5 +104,3 @@ $node->connect_fails(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 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.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -109,14 +129,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -124,20 +151,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -147,13 +190,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.31.0
v35-0003-nss-Add-NSS-specific-tests.patchtext/x-diff; charset=us-asciiDownload
From 6d28d68f1adc2041aef66ba3c70c269919002d8d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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 := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -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 PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +269,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +309,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +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/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 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;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $openssl = 1;
+ plan tests => 107;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ plan tests => 107;
}
else
{
- plan tests => 103;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -51,19 +60,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
-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 => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
+
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ qr/\QSSL error\E/,
+ "connect to server with incorrect key password configured");
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -91,7 +149,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -110,87 +168,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -200,15 +275,13 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
- expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
-);
+ expected_stderr => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -222,22 +295,18 @@ $node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
- expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
-);
+ expected_stderr => qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
- expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
-);
+ expected_stderr => qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -246,22 +315,19 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
- expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
-);
+ expected_stderr => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
- expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ expected_stderr => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -270,44 +336,40 @@ $node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
$node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
- expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
-);
+ expected_stderr => qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
- $common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ "$common_connstr sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
- expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ expected_stderr => qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl ssldatabase=ssl/nss/root+server_ca.crt__server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -325,29 +387,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -361,55 +440,67 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
- expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ expected_stderr =>qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
);
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping");
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping");
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN regex mapping");
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -447,18 +538,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -472,20 +564,23 @@ SKIP:
$node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
- expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/);
+ expected_stderr => qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/
+);
+
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/);
# 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 =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -506,9 +601,9 @@ $node->connect_ok(
);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -516,17 +611,16 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked|SSL error: Encountered end of file/);
# clean up
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;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 9 : 10;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 9;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -23,12 +42,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -47,7 +60,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -96,11 +109,9 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
- expected_stderr =>
- qr/channel binding required, but server authenticated client without channel binding/
-);
+ expected_stderr => qr/channel binding required, but server authenticated client without channel binding/);
# 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 = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 62b11b7632..f74caf67fe 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 19ccdd95f9..348e48af25 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -55,7 +55,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -167,46 +166,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
- my $pgdata = $node->data_dir;
-
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
+ my $node = shift;
+ my %params = @_;
+ my $pgdata = $node->data_dir;
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.31.0
v35-0004-nss-pg_strong_random-support.patchtext/x-diff; charset=us-asciiDownload
From 6fdd9d715ee04ec337ca4f4602d99fe9c87d2394 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.31.0
v35-0005-nss-Documentation.patchtext/x-diff; charset=us-asciiDownload
From 3a37e9ef8ce5e260ed391fb0b69862d4eaee6b5f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0c9128a55d..6327c20faa 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1309,6 +1309,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -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
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1527,8 +1547,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 66ad4ba938..3cbd125d59 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -976,14 +976,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure that
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 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);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
-
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
- </para>
</listitem>
</varlistentry>
@@ -1808,8 +1969,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1824,8 +1985,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2587,6 +2748,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2610,9 +2773,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2639,6 +2808,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8626,6 +8804,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.31.0
v35-0006-nss-Support-NSS-in-pgcrypto.patchtext/x-diff; charset=us-asciiDownload
From 4274778a9b4a9eaecbd3efec34442fbaff786a59 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
- </row>
- <row>
- <entry>DES/3DES/CAST5</entry>
- <entry>no</entry>
<entry>yes</entry>
</row>
+ <row>
+ <entry>DES/3DES</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
+ </row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
--
2.31.0
v35-0007-nss-Support-NSS-in-sslinfo.patchtext/x-diff; charset=us-asciiDownload
From e70018382ae097cfb8313fd9514ccb036d10f54e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
--
2.31.0
v35-0008-nss-Support-NSS-in-cryptohash.patchtext/x-diff; charset=us-asciiDownload
From 18f43ccdd24742c9da382a298accc8c4a4bec3d1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.31.0
v35-0009-nss-Build-infrastructure.patchtext/x-diff; charset=us-asciiDownload
From a11b265a057effeac31f666e0b1e2ac9f697050e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
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
+/* 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
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/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 = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/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 += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/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=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/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 =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 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');
# 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);
# 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';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -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 => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -495,6 +498,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -549,6 +553,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -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');
--
2.31.0
On Mon, Apr 05, 2021 at 11:12:22AM +0900, Michael Paquier wrote:
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.
For now, I have marked this patch set as returned with feedback as it
is still premature for integration, and there are still bugs in it.
FWIW, I think that there is a future for providing an alternative to
OpenSSL, so, even if it could not make it for this release, I'd like
to push forward with this area more seriously as of 15. The recent
libcrypto-related refactorings were one step in this direction, as
well.
--
Michael
On 25 Mar 2021, at 00:56, Jacob Champion <pchampion@vmware.com> wrote:
Databases that are opened *after* the first one are given their own separate slots. Any certificates that are part of those databases seemingly can't be referenced directly by nickname. They have to be prefixed by their token name -- a name which you don't have if you used NSS_InitContext() to create the database. You have to use SECMOD_OpenUserDB() instead. This explains some strange failures I was seeing in local testing, where the order of InitContext determined whether our client certificate selection succeeded or failed.
Sorry for the latency is responding, but I'm now back from parental leave.
AFAICT the tokenname for the database can be set with the dbTokenDescription
member in the NSSInitParameters struct passed to NSS_InitContext() (documented
in nss.h). Using this we can avoid the messier SECMOD machinery and use the
token in the auth callback to refer to the database we loaded. I hacked this
up in my local tree (rebased patchset coming soon) and it seems to work as
intended.
--
Daniel Gustafsson https://vmware.com/
Attached is a rebase to keep bitrot at bay. On top rebasing and smaller fixes
in comments etc, this version fixes/adds a number things:
* Performs DN resolution to support the DN mapping
* Locks the SECMOD parts and PR_Init call in the frontend as per Jacobs
findings upthread
* Properly set the tokenname of the database to avoid ambigious lookups in case
multiple databases are loaded (a better name to ensure uniqueness is a TODO)
* Adds a test for certificate lookup without sslcert set
Attachments:
v36-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v36-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From d64c6fae1bde41dbe9e037a77756581fa72f3df3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v36 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index e9b98f442f..c9feda95fb 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 3b42d8bdc9..80939ede97 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 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
+/* 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
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index de22c9ba2c..29a9ce3663 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 233ddbf4c2..d4526831d6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -141,6 +141,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');
@@ -206,12 +212,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -269,12 +282,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,9 +462,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +499,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 97f012bbb5..d00abeb471 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -305,10 +305,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -498,6 +501,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -558,6 +562,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.30.1 (Apple Git-130)
v36-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v36-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From bdf3c2c0dcad906490095743e6cc6b0cc5955a53 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v36 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.30.1 (Apple Git-130)
v36-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v36-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 6b17581f8b285fa52f9fcd4a390e4652ef014e55 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v36 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.30.1 (Apple Git-130)
v36-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v36-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 72fc8feb103d59070da4410354ec97c44dc22eb1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v36 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 13770dfc6f..1896eda9e3 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.30.1 (Apple Git-130)
v36-0005-nss-Documentation.patchapplication/octet-stream; name=v36-0005-nss-Documentation.patch; x-unix-mode=0644Download
From cf6d1b3b2fff74e1c12638cc816384ae9c1e8d02 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v36 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 | 337 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 463 insertions(+), 92 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 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3b7da468b7..18d9d4f58d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1309,6 +1309,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -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
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1537,8 +1557,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 3c0aa118c7..96b12f89bf 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -995,14 +995,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 875950b83c..e6efd736c6 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8648,6 +8821,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8674,6 +8852,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.30.1 (Apple Git-130)
v36-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v36-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 31cc71c210bbea1635b0d7a1f0eeeda28f85fe65 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v36 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.30.1 (Apple Git-130)
v36-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v36-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 2923363078814f07bc8acedf4ac959f34555d003 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v36 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 | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 608 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a517756c94..14ca1f8bf3 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,36 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +70,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +101,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +143,86 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -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 PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +269,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +309,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +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/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 4b5d4ba46a..4105a67b94 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Encountered end of file/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9a86020a79..adfdc55e1a 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v36-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v36-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 2ae7a6ffbe675342f4c092f88962161886f1cbf6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v36 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 44daefb002..4b5d4ba46a 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9143fa515f..9a86020a79 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v36-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v36-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 648909cf6059d604ce6e67c8cb84dfa249fd6c55 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v36 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
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1517 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 +++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1138 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3058 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7df30010f2..3faaac3dbe 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9122,7 +9122,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 68372fcea8..52a868d6cd 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2898,7 +2898,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..43fd3d0a09
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1517 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalant of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 87bc688704..14bc566116 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4391,7 +4391,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4459,6 +4463,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4487,8 +4501,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..6cbfe91927 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -219,6 +219,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +305,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +315,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 27da86e5e0..d870346ce0 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80703698b8..8b96b87e92 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -345,6 +345,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..1b9bd4f1a5
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1138 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 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 PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e81dc37906..4e1d99ede1 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 */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -384,6 +388,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -524,6 +529,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -785,7 +812,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.30.1 (Apple Git-130)
On Tue, 2020-10-27 at 23:39 -0700, Andres Freund wrote:
Maybe we should just have --with-ssl={openssl,nss}? That'd avoid
needing
to check for errors.
[ apologies for the late reply ]
Would it be more proper to call it --with-tls={openssl,nss} ?
Regards,
Jeff Davis
On 3 Jun 2021, at 19:37, Jeff Davis <pgsql@j-davis.com> wrote:
On Tue, 2020-10-27 at 23:39 -0700, Andres Freund wrote:
Maybe we should just have --with-ssl={openssl,nss}? That'd avoid
needing
to check for errors.[ apologies for the late reply ]
Would it be more proper to call it --with-tls={openssl,nss} ?
Well, we use SSL for everything else (GUCs, connection params and env vars etc)
so I think --with-ssl is sensible.
However, SSL and TLS are used quite interchangeably these days so I think it
makes sense to provide --with-tls as an alias.
--
Daniel Gustafsson https://vmware.com/
On 6/3/21 1:47 PM, Daniel Gustafsson wrote:
On 3 Jun 2021, at 19:37, Jeff Davis <pgsql@j-davis.com> wrote:
On Tue, 2020-10-27 at 23:39 -0700, Andres Freund wrote:
Maybe we should just have --with-ssl={openssl,nss}? That'd avoid
needing
to check for errors.[ apologies for the late reply ]
Would it be more proper to call it --with-tls={openssl,nss} ?
Well, we use SSL for everything else (GUCs, connection params and env vars etc)
so I think --with-ssl is sensible.However, SSL and TLS are used quite interchangeably these days so I think it
makes sense to provide --with-tls as an alias.
Yeah, but it's annoying to have to start every talk I give touching this
subject with the slide that says "When we say SSL we really means TLS".
Maybe release 15 would be a good time to rename user-visible option
names etc, with support for legacy names.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Thu, 2021-06-03 at 15:53 -0400, Andrew Dunstan wrote:
Yeah, but it's annoying to have to start every talk I give touching
this
subject with the slide that says "When we say SSL we really means
TLS".
Maybe release 15 would be a good time to rename user-visible option
names etc, with support for legacy names.
Sounds good to me, though I haven't looked into how big of a diff that
will be.
Also, do we have precedent for GUC aliases? That might be a little
weird.
Regards,
Jeff Davis
On 3 Jun 2021, at 22:14, Jeff Davis <pgsql@j-davis.com> wrote:
On Thu, 2021-06-03 at 15:53 -0400, Andrew Dunstan wrote:
Yeah, but it's annoying to have to start every talk I give touching
this
subject with the slide that says "When we say SSL we really means
TLS".
Maybe release 15 would be a good time to rename user-visible option
names etc, with support for legacy names.
Perhaps. Having spent some time in this space, SSL has IMHO become the de
facto term for an encrypted connection at the socket layer, with TLS being the
current protocol suite (additionally, often referred to SSL/TLS). Offering
tls* counterparts to our ssl GUCs etc will offer a level of correctness but I
doubt we'll ever get rid of ssl* so we might not help too many users by the
added complexity.
It might also put us a hard spot if the next TLS spec ends up being called
something other than TLS? It's clearly happened before =)
Sounds good to me, though I haven't looked into how big of a diff that
will be.Also, do we have precedent for GUC aliases? That might be a little
weird.
I don't think we do currently, but I have a feeling the topic has surfaced here
before.
If we end up settling on this being something we want I can volunteer to do the
legwork, but it seems a discussion best had before a patch is drafted.
--
Daniel Gustafsson https://vmware.com/
Daniel Gustafsson <daniel@yesql.se> writes:
It might also put us a hard spot if the next TLS spec ends up being called
something other than TLS? It's clearly happened before =)
Good point. I'm inclined to just stick with the SSL terminology.
Also, do we have precedent for GUC aliases? That might be a little
weird.
I don't think we do currently, but I have a feeling the topic has surfaced here
before.
We do, look for "sort_mem" in guc.c. So it's not like it'd be
inconvenient to implement. But I think user confusion and the
potential for the new terminology to fail to be any more
future-proof are good reasons to just leave the names alone.
regards, tom lane
On 3 Jun 2021, at 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Also, do we have precedent for GUC aliases? That might be a little
weird.I don't think we do currently, but I have a feeling the topic has surfaced here
before.We do, look for "sort_mem" in guc.c.
I knew it seemed familiar but I failed to find it, thanks for the pointer.
--
Daniel Gustafsson https://vmware.com/
On Thu, Jun 3, 2021 at 04:55:45PM -0400, Tom Lane wrote:
Daniel Gustafsson <daniel@yesql.se> writes:
It might also put us a hard spot if the next TLS spec ends up being called
something other than TLS? It's clearly happened before =)Good point. I'm inclined to just stick with the SSL terminology.
I wonder if we should use SSL/TLS in more places in our documentation.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
If only the physical world exists, free will is an illusion.
Bruce Momjian <bruce@momjian.us> writes:
I wonder if we should use SSL/TLS in more places in our documentation.
No objection to doing that in the docs; I'm just questioning
switching the code-visible names.
regards, tom lane
On 3 Jun 2021, at 23:11, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Bruce Momjian <bruce@momjian.us> writes:
I wonder if we should use SSL/TLS in more places in our documentation.
No objection to doing that in the docs; I'm just questioning
switching the code-visible names.
As long as it's still searchable by "SSL", "TLS" and "SSL/TLS" and not just the
latter.
--
Daniel Gustafsson https://vmware.com/
On Thu, Jun 3, 2021 at 11:14 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 3 Jun 2021, at 23:11, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Bruce Momjian <bruce@momjian.us> writes:
I wonder if we should use SSL/TLS in more places in our documentation.
No objection to doing that in the docs; I'm just questioning
switching the code-visible names.
+1.
I also don't think it's worth changing the actual names, I think
that'll cause more problems than it solves. But we can, and probably
should, change the messaging around it, particularly the docs (but
probably also comments in the config file).
As long as it's still searchable by "SSL", "TLS" and "SSL/TLS" and not just the
latter.
Agreed, making it searchable and easily cross-linkable.. And maybe
both terms should be in the glossary.
--
Magnus Hagander
Me: https://www.hagander.net/
Work: https://www.redpill-linpro.com/
On Fri, 2021-05-28 at 11:04 +0200, Daniel Gustafsson wrote:
Attached is a rebase to keep bitrot at bay.
I get a failure during one of the CRL directory tests due to a missing
database -- it looks like the Makefile is missing an entry. (I'm
dusting off my build after a few months away, so I don't know if this
latest rebase introduced it or not.)
Attached is a quick patch; does it work on your machine?
--Jacob
Attachments:
nss-fix-crldir.patch.txttext/plain; name=nss-fix-crldir.patch.txtDownload
From 7a7b8904ef22212190bb988fab1ef696fe1a59de Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 14 Jun 2021 15:04:26 -0700
Subject: [PATCH] test/ssl: fix NSS server-side CRL test
Make sure the database is created during `make nssfiles`, and expect a
revocation failure message.
---
src/test/ssl/Makefile | 2 ++
src/test/ssl/t/001_ssltests.pl | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 14ca1f8bf3..557cbe223f 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -45,6 +45,7 @@ NSSFILES := ssl/nss/client_ca.crt.db \
ssl/nss/client-revoked.crt__client-revoked.key.db \
ssl/nss/server-cn-only.crt__server-password.key.db \
ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
ssl/nss/root.crl \
ssl/nss/server.crl \
ssl/nss/client.crl \
@@ -167,6 +168,7 @@ ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/se
pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
for c in $(shell ls ssl/root+client-crldir) ; do \
echo $${c} ; \
openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 4105a67b94..aec99e7bf6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -664,7 +664,7 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
expected_stderr =>
- qr/SSL error: sslv3 alert certificate revoked|SSL error: Encountered end of file/);
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
--
2.25.1
On 15 Jun 2021, at 00:15, Jacob Champion <pchampion@vmware.com> wrote:
Attached is a quick patch; does it work on your machine?
It does, thanks! I've included it in the attached v37 along with a few tiny
non-functional improvements in comment spelling etc.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v37-0009-nss-Build-infrastructure.patchapplication/octet-stream; name=v37-0009-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From c7a4cf6d34f27bea8757231e13696d11cb980dce Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v37 9/9] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index e9b98f442f..c9feda95fb 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 3b42d8bdc9..80939ede97 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 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
+/* 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
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index de22c9ba2c..29a9ce3663 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 233ddbf4c2..d4526831d6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -141,6 +141,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');
@@ -206,12 +212,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -269,12 +282,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,9 +462,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +499,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 a7b8f720b5..f5b814ced9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -305,10 +305,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -498,6 +501,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -558,6 +562,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.30.1 (Apple Git-130)
v37-0008-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v37-0008-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 0c9ddae5b3eabc4ee3bdf02f76cfff4e0cdbeab4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v37 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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.30.1 (Apple Git-130)
v37-0007-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v37-0007-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 92a823d350d28c918f42bac551dfbea718104812 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v37 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.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.30.1 (Apple Git-130)
v37-0006-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v37-0006-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 4adbc2e72657911a1d05fa97568be15d36da6bf5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v37 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.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index c4dce94001..0adcd67f87 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.30.1 (Apple Git-130)
v37-0005-nss-Documentation.patchapplication/octet-stream; name=v37-0005-nss-Documentation.patch; x-unix-mode=0644Download
From 987f31d66c8dc80338e564eeaf1bc7121b8a912c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v37 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 | 337 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
5 files changed, 463 insertions(+), 92 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 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index aa3e178240..30fff244e8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1309,6 +1309,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -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
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1537,8 +1557,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 3c0aa118c7..96b12f89bf 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -995,14 +995,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6b96f30dcc..bcc56edf83 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,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.
- </para>
- <para>
- The directory needs to be prepared with the OpenSSL command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the OpenSSL command <literal>openssl rehash</literal>
+ or <literal>c_rehash</literal>. See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8676,6 +8849,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.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
@@ -8702,6 +8880,11 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
--
2.30.1 (Apple Git-130)
v37-0004-nss-pg_strong_random-support.patchapplication/octet-stream; name=v37-0004-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From b4f97bc1d7b5b7d57b5773efb07684acf915e816 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v37 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;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.30.1 (Apple Git-130)
v37-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v37-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 17ecc0b8c092786e162e3ac574da91e17505705e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v37 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 | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a517756c94..557cbe223f 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +144,87 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -98,6 +243,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +271,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +311,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +370,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 4b5d4ba46a..aec99e7bf6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9a86020a79..adfdc55e1a 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v37-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v37-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 82faf47cdf0be534f98cf88ec584b5f48fac0437 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v37 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/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 44daefb002..4b5d4ba46a 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9143fa515f..9a86020a79 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v37-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v37-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From d2e0eac439b512d19f7bd1cb32b78d1d6c969c55 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v37 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
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1517 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 +++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 10 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1138 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3058 insertions(+), 5 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 1fb26639fc..89d9699e38 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9233,7 +9233,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 68372fcea8..52a868d6cd 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2898,7 +2898,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..9fcf36ab13
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1517 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 68b62d523d..6ca931c309 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4391,7 +4391,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4459,6 +4463,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4487,8 +4501,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..6cbfe91927 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -219,6 +219,10 @@ typedef struct Port
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +305,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +315,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 27da86e5e0..d870346ce0 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80703698b8..8b96b87e92 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -345,6 +345,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..1b9bd4f1a5
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1138 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->nss_context)
+ {
+ NSS_ShutdownContext(conn->nss_context);
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * 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 = 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 = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ return NULL;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = 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 considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ 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 invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection param.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ec378705ad..72c0850dea 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -655,6 +655,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e81dc37906..4e1d99ede1 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 */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -384,6 +388,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -524,6 +529,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -785,7 +812,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.30.1 (Apple Git-130)
On Wed, 2021-06-16 at 00:08 +0200, Daniel Gustafsson wrote:
On 15 Jun 2021, at 00:15, Jacob Champion <pchampion@vmware.com> wrote:
Attached is a quick patch; does it work on your machine?It does, thanks! I've included it in the attached v37 along with a few tiny
non-functional improvements in comment spelling etc.
Great, thanks!
I've been tracking down reference leaks in the client. These open
references prevent NSS from shutting down cleanly, which then makes it
impossible to open a new context in the future. This probably affects
other libpq clients more than it affects psql.
The first step to fixing that is not ignoring failures during NSS
shutdown, so I've tried a patch to pgtls_close() that pushes any
failures through the pqInternalNotice(). That seems to be working well.
The tests were still mostly green, so I taught connect_ok() to fail if
any stderr showed up, and that exposed quite a few failures.
I am currently stuck on one last failing test. This leak seems to only
show up when using TLSv1.2 or below. There doesn't seem to be a
substantial difference in libpq code coverage between 1.2 and 1.3, so
I'm worried that either 1) there's some API we use that "requires"
cleanup, but only on 1.2 and below, or 2) there's some bug in my
version of NSS.
Attached are a few work-in-progress patches. I think the reference
cleanups themselves are probably solid, but the rest of it could use
some feedback. Are there better ways to test for this? and can anyone
reproduce the TLSv1.2 leak?
--Jacob
Attachments:
0001-nss-don-t-ignore-failures-during-context-shutdown.patch.txttext/plain; name=0001-nss-don-t-ignore-failures-during-context-shutdown.patch.txtDownload
From 6976d15a4be50276ea2f77fd5c37d238493f9447 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 10:01:14 -0700
Subject: [PATCH 1/3] nss: don't ignore failures during context shutdown
The biggest culprit of a shutdown failure so far seems to be object
leaks. A failure here may prevent future contexts from being created
(and they'll fail in confusing ways), so don't ignore it.
There's not a great way to signal errors from this layer of the stack,
but a notice will at least give us a chance of seeing the problem.
---
src/interfaces/libpq/fe-secure-nss.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 1b9bd4f1a5..90f57e0d99 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -139,7 +139,16 @@ pgtls_close(PGconn *conn)
{
if (conn->nss_context)
{
- NSS_ShutdownContext(conn->nss_context);
+ SECStatus status;
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
conn->nss_context = NULL;
}
}
--
2.25.1
0002-test-check-for-empty-stderr-during-connect_ok.patch.txttext/plain; name=0002-test-check-for-empty-stderr-during-connect_ok.patch.txtDownload
From acabc4d51d38124db748a6be9dca0ff83693e039 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 14:37:28 -0700
Subject: [PATCH 2/3] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 427a360198..327dfb4ed9 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index f080a0ccba..d6508df8bd 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index b5594924ca..62015339a0 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 0ae14e4c85..e857c26258 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 2027cbf43d..4a1e151d7e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2018,8 +2018,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index aec99e7bf6..c80abc9ec9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index adfdc55e1a..7976b2e5b1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.25.1
0003-nss-clean-up-leaked-resources.patch.txttext/plain; name=0003-nss-clean-up-leaked-resources.patch.txtDownload
From 3141b29053a4b48289ebb7ee26d22e395c07ff31 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 15:59:39 -0700
Subject: [PATCH 3/3] nss: clean up leaked resources
The NSPR file descriptor and a handful of certificate references were
preventing NSS from shutting down.
At least one leak is still on the loose, when forcing TLSv1.2 or lower
during client connections:
$ psql 'host=localhost sslmode=require ssl_max_protocol_version=TLSv1.2'
psql (14beta1)
SSL connection (protocol: TLSv1.2, cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, bits: 128, compression: off)
Type "help" for help.
postgres=# \q
unable to shut down NSS context: NSS could not shutdown. Objects are still in use.
---
src/interfaces/libpq/fe-secure-nss.c | 37 ++++++++++++++++++++++------
1 file changed, 30 insertions(+), 7 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 90f57e0d99..7b92e89ecd 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -137,6 +137,22 @@ pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
void
pgtls_close(PGconn *conn)
{
+ /* All NSS references must be cleaned up before we close out the context. */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
if (conn->nss_context)
{
SECStatus status;
@@ -573,16 +589,16 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
SECOidTag signature_tag;
SECOidTag digest_alg;
int digest_len;
- PLArenaPool *arena;
+ PLArenaPool *arena = NULL;
SECItem digest;
- char *ret;
+ char *ret = NULL;
SECStatus status;
*len = 0;
server_cert = SSL_PeerCertificate(conn->pr_fd);
if (!server_cert)
- return NULL;
+ goto cleanup;
signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
@@ -590,7 +606,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not find digest for OID '%s'\n"),
SECOID_FindOIDTagDescription(signature_tag));
- return NULL;
+ goto cleanup;
}
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
@@ -605,14 +621,18 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unable to generate peer certificate digest: %s"),
pg_SSLerrmessage(PR_GetError()));
- PORT_FreeArena(arena, PR_TRUE);
- return NULL;
+ goto cleanup;
}
ret = pg_malloc(digest.len);
memcpy(ret, digest.data, digest.len);
*len = digest_len;
- PORT_FreeArena(arena, PR_TRUE);
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
return ret;
}
@@ -712,6 +732,8 @@ done:
/* san_list will be freed by freeing the arena it was allocated in */
if (arena)
PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
PR_Free(server_hostname);
if (status == SECSuccess)
@@ -826,6 +848,7 @@ pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer
pg_SSLerrmessage(PR_GetError()));
}
+ CERT_DestroyCertificate(server_cert);
return status;
}
--
2.25.1
On 16 Jun 2021, at 01:50, Jacob Champion <pchampion@vmware.com> wrote:
I've been tracking down reference leaks in the client. These open
references prevent NSS from shutting down cleanly, which then makes it
impossible to open a new context in the future. This probably affects
other libpq clients more than it affects psql.
Ah, nice catch, that's indeed a bug in the frontend implementation. The
problem is that the NSS trustdomain cache *must* be empty before shutting down
the context, else this very issue happens. Note this in be_tls_destroy():
/*
* 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();
Calling SSL_ClearSessionCache() in pgtls_close() fixes the error.
There is another resource leak left (visible in one test after the above is
added), the SECMOD module needs to be unloaded in case it's been loaded.
Implementing that with SECMOD_UnloadUserModule trips a segfault in NSS which I
have yet to figure out (when acquiring a lock with NSSRWLock_LockRead).
The first step to fixing that is not ignoring failures during NSS
shutdown, so I've tried a patch to pgtls_close() that pushes any
failures through the pqInternalNotice(). That seems to be working well.
I'm keeping these in during hacking, with a comment that they need to be
revisited during review since they are mainly useful for debugging.
The tests were still mostly green, so I taught connect_ok() to fail if
any stderr showed up, and that exposed quite a few failures.
With your patches I'm seeing a couple of these:
SSL error: The one-time function was previously called and failed. Its error code is no longer available
This is an error from NSPR, but it's not clear to me which PR_CallOnce call
it's coming from. It seems to be hitting in the SAN and CRL tests, so it
smells of some form of caching implemented with NSPR API's to me but thats a
mere hunch.
I am currently stuck on one last failing test. This leak seems to only
show up when using TLSv1.2 or below.
AFAICT the session cache is avoided for TLSv1.3 due to 1.3 not supporting
renegotiation.
--
Daniel Gustafsson https://vmware.com/
On Wed, 2021-06-16 at 15:31 +0200, Daniel Gustafsson wrote:
On 16 Jun 2021, at 01:50, Jacob Champion <pchampion@vmware.com> wrote:
I've been tracking down reference leaks in the client. These open
references prevent NSS from shutting down cleanly, which then makes it
impossible to open a new context in the future. This probably affects
other libpq clients more than it affects psql.Ah, nice catch, that's indeed a bug in the frontend implementation. The
problem is that the NSS trustdomain cache *must* be empty before shutting down
the context, else this very issue happens. Note this in be_tls_destroy():/*
* 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();Calling SSL_ClearSessionCache() in pgtls_close() fixes the error.
That's unfortunate. The session cache is global, right? So I'm guessing
we'll need to refcount and lock that call, to avoid cleaning up out
from under a thread that's actively using the the cache?
There is another resource leak left (visible in one test after the above is
added), the SECMOD module needs to be unloaded in case it's been loaded.
Implementing that with SECMOD_UnloadUserModule trips a segfault in NSS which I
have yet to figure out (when acquiring a lock with NSSRWLock_LockRead).[...]
With your patches I'm seeing a couple of these:
SSL error: The one-time function was previously called and failed. Its error code is no longer available
Hmm. Adding SSL_ClearSessionCache() (without thread-safety at the
moment) fixes all of the SSL tests for me, and I don't see either the
SECMOD leak or the "one-time function" error that you've mentioned.
What version of NSS are you running? I'm on 3.63.
I've attached my current patchset (based on v37) for comparison.
I am currently stuck on one last failing test. This leak seems to only
show up when using TLSv1.2 or below.AFAICT the session cache is avoided for TLSv1.3 due to 1.3 not supporting
renegotiation.
Nice, at least that mystery is solved. :D
Thanks,
--Jacob
Attachments:
0001-nss-don-t-ignore-failures-during-context-shutdown.patch.txttext/plain; name=0001-nss-don-t-ignore-failures-during-context-shutdown.patch.txtDownload
From 6976d15a4be50276ea2f77fd5c37d238493f9447 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 10:01:14 -0700
Subject: [PATCH 1/3] nss: don't ignore failures during context shutdown
The biggest culprit of a shutdown failure so far seems to be object
leaks. A failure here may prevent future contexts from being created
(and they'll fail in confusing ways), so don't ignore it.
There's not a great way to signal errors from this layer of the stack,
but a notice will at least give us a chance of seeing the problem.
---
src/interfaces/libpq/fe-secure-nss.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 1b9bd4f1a5..90f57e0d99 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -139,7 +139,16 @@ pgtls_close(PGconn *conn)
{
if (conn->nss_context)
{
- NSS_ShutdownContext(conn->nss_context);
+ SECStatus status;
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
conn->nss_context = NULL;
}
}
--
2.25.1
0002-test-check-for-empty-stderr-during-connect_ok.patch.txttext/plain; name=0002-test-check-for-empty-stderr-during-connect_ok.patch.txtDownload
From acabc4d51d38124db748a6be9dca0ff83693e039 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 14:37:28 -0700
Subject: [PATCH 2/3] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 427a360198..327dfb4ed9 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index f080a0ccba..d6508df8bd 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index b5594924ca..62015339a0 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 0ae14e4c85..e857c26258 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 2027cbf43d..4a1e151d7e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2018,8 +2018,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index aec99e7bf6..c80abc9ec9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index adfdc55e1a..7976b2e5b1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.25.1
0003-nss-clean-up-leaked-resources.patch.txttext/plain; name=0003-nss-clean-up-leaked-resources.patch.txtDownload
From 16cad09e1c534391c6aad5c8f552da6a7e6949fd Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 15:59:39 -0700
Subject: [PATCH 3/3] nss: clean up leaked resources
The NSPR file descriptor, a handful of certificate references, and
session cache entries were preventing NSS from shutting down.
---
src/interfaces/libpq/fe-secure-nss.c | 44 +++++++++++++++++++++++-----
1 file changed, 37 insertions(+), 7 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 90f57e0d99..e9dacdc732 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -137,9 +137,32 @@ pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
void
pgtls_close(PGconn *conn)
{
+ /* All NSS references must be cleaned up before we close out the context. */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
if (conn->nss_context)
{
SECStatus status;
+
+ /*
+ * The session cache must be cleared, or we'll leak references.
+ * TODO: refcount maybe? This has a global effect.
+ */
+ SSL_ClearSessionCache();
+
status = NSS_ShutdownContext(conn->nss_context);
if (status != SECSuccess)
@@ -573,16 +596,16 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
SECOidTag signature_tag;
SECOidTag digest_alg;
int digest_len;
- PLArenaPool *arena;
+ PLArenaPool *arena = NULL;
SECItem digest;
- char *ret;
+ char *ret = NULL;
SECStatus status;
*len = 0;
server_cert = SSL_PeerCertificate(conn->pr_fd);
if (!server_cert)
- return NULL;
+ goto cleanup;
signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
@@ -590,7 +613,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not find digest for OID '%s'\n"),
SECOID_FindOIDTagDescription(signature_tag));
- return NULL;
+ goto cleanup;
}
arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
@@ -605,14 +628,18 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unable to generate peer certificate digest: %s"),
pg_SSLerrmessage(PR_GetError()));
- PORT_FreeArena(arena, PR_TRUE);
- return NULL;
+ goto cleanup;
}
ret = pg_malloc(digest.len);
memcpy(ret, digest.data, digest.len);
*len = digest_len;
- PORT_FreeArena(arena, PR_TRUE);
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
return ret;
}
@@ -712,6 +739,8 @@ done:
/* san_list will be freed by freeing the arena it was allocated in */
if (arena)
PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
PR_Free(server_hostname);
if (status == SECSuccess)
@@ -826,6 +855,7 @@ pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer
pg_SSLerrmessage(PR_GetError()));
}
+ CERT_DestroyCertificate(server_cert);
return status;
}
--
2.25.1
On 16 Jun 2021, at 18:15, Jacob Champion <pchampion@vmware.com> wrote:
On Wed, 2021-06-16 at 15:31 +0200, Daniel Gustafsson wrote:
On 16 Jun 2021, at 01:50, Jacob Champion <pchampion@vmware.com> wrote:
I've been tracking down reference leaks in the client. These open
references prevent NSS from shutting down cleanly, which then makes it
impossible to open a new context in the future. This probably affects
other libpq clients more than it affects psql.Ah, nice catch, that's indeed a bug in the frontend implementation. The
problem is that the NSS trustdomain cache *must* be empty before shutting down
the context, else this very issue happens. Note this in be_tls_destroy():/*
* 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();Calling SSL_ClearSessionCache() in pgtls_close() fixes the error.
That's unfortunate. The session cache is global, right? So I'm guessing
we'll need to refcount and lock that call, to avoid cleaning up out
from under a thread that's actively using the the cache?
I'm not sure, the documentation doesn't give any answers and implementations of
libnss tend to just clear the cache without consideration. In libcurl we do
just that, and haven't had any complaints - which doesn't mean it's correct but
it's a datapoint.
There is another resource leak left (visible in one test after the above is
added), the SECMOD module needs to be unloaded in case it's been loaded.
Implementing that with SECMOD_UnloadUserModule trips a segfault in NSS which I
have yet to figure out (when acquiring a lock with NSSRWLock_LockRead).[...]
With your patches I'm seeing a couple of these:
SSL error: The one-time function was previously called and failed. Its error code is no longer available
Hmm. Adding SSL_ClearSessionCache() (without thread-safety at the
moment) fixes all of the SSL tests for me, and I don't see either the
SECMOD leak or the "one-time function" error that you've mentioned.
Reading the code I don't think a loaded user module is considered a resource
that must've been released prior to closing the context. I will dig for what
showed up in my tests, but I don't think it was caused by this.
What version of NSS are you running? I'm on 3.63.
Right now I'm using what Debian 10 is packaging which is 3.42. Admittedly not
hot off the press but I've been trying to develop off a packaged version which
we might see users wanting to deploy against should this get shipped.
--
Daniel Gustafsson https://vmware.com/
Attached is a rebased version which incorporates your recent patchset for
resource handling, as well as the connect_ok test patch.
I've implemented tracking the close_notify alert that you mentioned offlist,
but it turns out that the alert callbacks in NSS are of limited use so it
close_notify is currently the only checked description. The enum which labels
the descriptions in the SSLAlert struct is private, so it's just sending over
an anonymous number apart from close_notify which is zero.
A few other fixups are included as well, like adapting the pending data read
function in the frontend to how the OpenSSL implementation does it.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v38-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v38-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 2947ef5c31b6c768c0e9b1958447d0eff28632d0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v38 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a517756c94..557cbe223f 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +144,87 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -98,6 +243,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +271,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +311,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +370,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 4b5d4ba46a..aec99e7bf6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9a86020a79..adfdc55e1a 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v38-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v38-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From e84597e1b6bc83cfbd5668bc86f9653a2a6ef015 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v38 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 44daefb002..4b5d4ba46a 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9143fa515f..9a86020a79 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v38-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v38-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 3b22ea1e2b1a133b3f274b5de96f8733d97c7cd2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v38 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1182 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3162 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index cc1cca3006..2514d55886 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9236,7 +9236,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 68372fcea8..52a868d6cd 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2898,7 +2898,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 68b62d523d..6ca931c309 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4391,7 +4391,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4459,6 +4463,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4487,8 +4501,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 27da86e5e0..d870346ce0 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 80703698b8..8b96b87e92 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -345,6 +345,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..613e7d6a1d
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1182 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ SSL_ClearSessionCache();
+
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ return SECFailure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ return SECFailure;
+
+ err = 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 warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index cc6032b15b..a98d8a865a 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -664,6 +664,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e81dc37906..4e1d99ede1 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 */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -384,6 +388,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -524,6 +529,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -785,7 +812,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.30.1 (Apple Git-130)
v38-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v38-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From b7a89442ce483da644a7c9f97fbbf985dbb5cb8c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v38 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index a2c055f3cc..caeb4697dc 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12671,8 +12673,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13645,6 +13913,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18424,6 +18709,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18453,7 +18741,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 3eb35583c1..bff96d2695 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" = x"" ; then
with_ssl=no
fi
@@ -1264,8 +1264,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1451,6 +1471,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2212,6 +2235,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2221,7 +2246,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 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
+/* 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
@@ -343,6 +349,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -920,6 +929,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0c4e55b6ad..4d90235ab2 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index de22c9ba2c..29a9ce3663 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 233ddbf4c2..d4526831d6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -141,6 +141,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');
@@ -206,12 +212,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -269,12 +282,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,9 +462,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +499,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 a7b8f720b5..f5b814ced9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -305,10 +305,13 @@ sub GenerateFiles
HAVE_LIBLDAP_R => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -498,6 +501,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -558,6 +562,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.30.1 (Apple Git-130)
v38-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v38-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 1991dedb341bd1a6259e8c02367dc610c2367e8f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v38 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.30.1 (Apple Git-130)
v38-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v38-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From fd1e2631401739bc204b2c5818ec33cf2dc6b9a4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v38 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.30.1 (Apple Git-130)
v38-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v38-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From c1bf60b97ae531eb6bd685b3fdd1b220660cafcb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v38 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index c4dce94001..0adcd67f87 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.30.1 (Apple Git-130)
v38-0006-nss-Documentation.patchapplication/octet-stream; name=v38-0006-nss-Documentation.patch; x-unix-mode=0644Download
From d85e2bdf5c8606fdd4b93c67012942884d7ebf22 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v38 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 13bd819eb1..7b02c8a622 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -441,6 +441,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -539,6 +561,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index eeba2caa43..e9848568fb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 3c0aa118c7..96b12f89bf 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -995,14 +995,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 441cc0da3a..5683ca91d4 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using openssl engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1830,8 +1991,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1846,8 +2007,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2609,6 +2770,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2632,9 +2795,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2661,6 +2830,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8677,6 +8850,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8703,6 +8882,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.30.1 (Apple Git-130)
v38-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v38-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 6819471c8f1f53190816ba0438248b6db1f6d89d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v38 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.30.1 (Apple Git-130)
v38-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v38-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 0412dc7e4c888b325651e7ec6b49e89b59e9c8aa Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v38 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 427a360198..327dfb4ed9 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index f080a0ccba..d6508df8bd 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index b5594924ca..62015339a0 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 0ae14e4c85..e857c26258 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 2027cbf43d..4a1e151d7e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2018,8 +2018,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index aec99e7bf6..c80abc9ec9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index adfdc55e1a..7976b2e5b1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.30.1 (Apple Git-130)
On Wed, 2021-06-23 at 15:48 +0200, Daniel Gustafsson wrote:
Attached is a rebased version which incorporates your recent patchset for
resource handling, as well as the connect_ok test patch.
With v38 I do see the "one-time function was previously called and
failed" message you mentioned before, as well as some PR_Assert()
crashes. Looks like it's just due to the placement of
SSL_ClearSessionCache(); gating it behind the conn->nss_context check
ensures that we don't call it if no NSS context actually exists. Patch
attached (0001).
--
Continuing my jog around the patch... client connections will crash if
hostaddr is provided rather than host, because SSL_SetURL can't handle
a NULL argument. I'm running with 0002 to fix it for the moment, but
I'm not sure yet if it does the right thing for IP addresses, which the
OpenSSL side has a special case for.
Early EOFs coming from the server don't currently have their own error
message, which leads to a confusingly empty
connection to server at "127.0.0.1", port 47447 failed:
0003 adds one, to roughly match the corresponding OpenSSL message.
While I was fixing that I noticed that I was getting a "unable to
verify certificate" error message for the early EOF case, even with
sslmode=require. That error message is being printed to conn-
errorMessage during pg_cert_auth_handler(), even if we're not
verifying certificates, and then that message is included in later
unrelated failures. 0004 patches that.
--Jacob
Attachments:
0001-nss-move-SSL_ClearSessionCache.patchtext/x-patch; name=0001-nss-move-SSL_ClearSessionCache.patchDownload
From a675f4b6808c695d3691ad8a1a5e004ed71312c3 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Tue, 15 Jun 2021 15:59:39 -0700
Subject: [PATCH 1/4] nss: move SSL_ClearSessionCache
Don't clear the cache if no context exists.
---
src/interfaces/libpq/fe-secure-nss.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 613e7d6a1d..87e18fdde2 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -141,8 +141,6 @@ pgtls_close(PGconn *conn)
* All NSS references must be cleaned up before we close out the
* context.
*/
- SSL_ClearSessionCache();
-
if (conn->pr_fd)
{
PRStatus status;
@@ -161,6 +159,10 @@ pgtls_close(PGconn *conn)
if (conn->nss_context)
{
SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references. */
+ SSL_ClearSessionCache();
+
status = NSS_ShutdownContext(conn->nss_context);
if (status != SECSuccess)
--
2.25.1
0002-nss-handle-NULL-host.patchtext/x-patch; name=0002-nss-handle-NULL-host.patchDownload
From 2a23b00084538d9d9849a1ca46d054c1c84c53c3 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 19 Jul 2021 10:29:45 -0700
Subject: [PATCH 2/4] nss: handle NULL host
...in the case that we're using a hostaddr instead.
---
src/interfaces/libpq/fe-secure-nss.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 87e18fdde2..cc66f5bb8c 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -183,6 +183,7 @@ pgtls_open_client(PGconn *conn)
PRFileDesc *model;
NSSInitParameters params;
SSLVersionRange desired_range;
+ const char *host;
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
@@ -449,9 +450,11 @@ pgtls_open_client(PGconn *conn)
/*
* Specify which hostname we are expecting to talk to for the ClientHello
- * SNI extension.
+ * SNI extension. TODO: what if the host is an IP address?
*/
- SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+ host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
status = SSL_ForceHandshake(conn->pr_fd);
--
2.25.1
0003-nss-fix-spurious-unable-to-verify-certificate-messag.patchtext/x-patch; name=0003-nss-fix-spurious-unable-to-verify-certificate-messag.patchDownload
From 9caa50893fc3b8d3447ce14d4b93876aa7ffe91a Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 19 Jul 2021 12:14:00 -0700
Subject: [PATCH 3/4] nss: fix spurious "unable to verify certificate" message
That message gets written to conn->errorMessage even if sslmode isn't
verifying the certificate contents, which can lead to a confusing UX if
the connection later fails for some other reason.
---
src/interfaces/libpq/fe-secure-nss.c | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index cc66f5bb8c..4490fb03e4 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -858,12 +858,6 @@ pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer
if (!pq_verify_peer_name_matches_certificate(conn))
status = SECFailure;
}
- else
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("unable to verify certificate: %s"),
- pg_SSLerrmessage(PR_GetError()));
- }
CERT_DestroyCertificate(server_cert);
return status;
@@ -923,6 +917,8 @@ pg_bad_cert_handler(void *arg, PRFileDesc *fd)
if (!arg)
return SECFailure;
+ err = PORT_GetError();
+
/*
* For sslmodes other than verify-full and verify-ca we don't perform peer
* validation, so return immediately. sslmode require with a database
@@ -934,13 +930,11 @@ pg_bad_cert_handler(void *arg, PRFileDesc *fd)
if (conn->ssldatabase &&
strlen(conn->ssldatabase) > 0 &&
certificate_database_has_CA(conn))
- return SECFailure;
+ goto failure;
}
else if (strcmp(conn->sslmode, "verify-full") == 0 ||
strcmp(conn->sslmode, "verify-ca") == 0)
- return SECFailure;
-
- err = PORT_GetError();
+ goto failure;
/*
* TODO: these are relevant error codes that can occur in certificate
@@ -963,15 +957,20 @@ pg_bad_cert_handler(void *arg, PRFileDesc *fd)
case SEC_ERROR_CA_CERT_INVALID:
case SEC_ERROR_CERT_USAGES_INVALID:
case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
- return SECSuccess;
break;
default:
- return SECFailure;
+ goto failure;
break;
}
- /* Unreachable */
return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
}
/* ------------------------------------------------------------ */
--
2.25.1
0004-nss-add-client-error-for-unexpected-EOF.patchtext/x-patch; name=0004-nss-add-client-error-for-unexpected-EOF.patchDownload
From 17f0cfc86a9b458d4cc964b6a1736f3b2870cf23 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 19 Jul 2021 12:16:17 -0700
Subject: [PATCH 4/4] nss: add client error for unexpected-EOF
...to mirror that of the OpenSSL implementation.
---
src/interfaces/libpq/fe-secure-nss.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 4490fb03e4..67dff8b724 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -488,6 +488,8 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
if (nread == 0)
{
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
read_errno = ECONNRESET;
nread = -1;
}
--
2.25.1
On 19 Jul 2021, at 21:33, Jacob Champion <pchampion@vmware.com> wrote:
..client connections will crash if
hostaddr is provided rather than host, because SSL_SetURL can't handle
a NULL argument. I'm running with 0002 to fix it for the moment, but
I'm not sure yet if it does the right thing for IP addresses, which the
OpenSSL side has a special case for.
AFAICT the idea is to handle it in the cert auth callback, so I've added some
PoC code to check for sslsni there and updated the TODO comment to reflect
that.
I've applied your patches in the attached rebase which passes all tests for me.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v39-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v39-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 59a22b993b616cf9ed9d1a24c8cea701130cb92e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v39 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 233ddbf4c2..d4526831d6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -141,6 +141,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');
@@ -206,12 +212,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -269,12 +282,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,9 +462,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -474,6 +499,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.30.1 (Apple Git-130)
v39-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v39-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From dc9970314decf125c38f3028944b6369e631232a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v39 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.30.1 (Apple Git-130)
v39-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v39-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 2bd328678d49199f26b3d61a997fa39371390eb0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v39 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.30.1 (Apple Git-130)
v39-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v39-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 857e5487191a9e426b8d178467d2eeb9b0ad6f2b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v39 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index c4dce94001..0adcd67f87 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.30.1 (Apple Git-130)
v39-0006-nss-Documentation.patchapplication/octet-stream; name=v39-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 660eed21904d50a742f6698d7d17b05a9dc458b9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v39 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 610372b78a..ac9eddc287 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 56689ba873..9e4f678b0b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.30.1 (Apple Git-130)
v39-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v39-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From bea5a3ba27864a3d9ea5ea5743199feeb932ecce Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v39 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.30.1 (Apple Git-130)
v39-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v39-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From f17b7b9cbf00accfabe1fb4652d7aed923fa83ae Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v39 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 427a360198..327dfb4ed9 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index f080a0ccba..d6508df8bd 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index b5594924ca..62015339a0 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 0ae14e4c85..e857c26258 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index ed5b4a1c4b..ef233a4942 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2018,8 +2018,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index aec99e7bf6..c80abc9ec9 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index adfdc55e1a..7976b2e5b1 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.30.1 (Apple Git-130)
v39-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v39-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 3103a8a8199bcab51cb8d914db88c9bc4e0290db Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v39 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index a517756c94..557cbe223f 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -80,6 +144,87 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -98,6 +243,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -105,6 +271,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -137,19 +311,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -175,14 +370,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 4b5d4ba46a..aec99e7bf6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9a86020a79..adfdc55e1a 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v39-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v39-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 6f74eb4da7c11b761fc94a8eb62a5525bbac8303 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v39 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 44daefb002..4b5d4ba46a 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 9143fa515f..9a86020a79 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v39-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v39-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From fe338be144ecd1a63ab00cc557d37d3bafd337d3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v39 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1197 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3177 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ed25e7a743..b431fa8ec8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9258,7 +9258,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 8cc23ef7fb..98cfe05d61 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a2e0f8de7e..740ffc7ff6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4398,7 +4398,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4466,6 +4470,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4494,8 +4508,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e950b41374..aa39e63b66 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..0b297d3aed
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1197 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+ const char *host;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension. TODO: if the hostname is a literal IP address we skip
+ * setting it, and check for SNI status in the cert callback. Is this the
+ * correct course of action?
+ */
+ host = conn->connhost[conn->whichhost].host;
+ if (host && host[0] &&
+ !(strspn(host, "0123456789.") == strlen(host) ||
+ strchr(host, ':')))
+ SSL_SetURL(conn->pr_fd, host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* If SNI is enabled we must have a hostname set */
+ if (conn->sslsni && conn->sslsni[0])
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e9f214b61b..dc04025f7d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.30.1 (Apple Git-130)
Another rebase to work around the recent changes in the ssl Makefile.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v40-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v40-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 8c135f1df75fe86ea11987ed044c2595bd0f0c93 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v40 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 38a8599337..3157d057bf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3cb46832ab..ccc347db0e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.30.1 (Apple Git-130)
v40-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v40-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 34c78a980f387ecc78143536e33323a01f24e30d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v40 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.30.1 (Apple Git-130)
v40-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v40-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 4c8f759f664abe2c2bb98bb346aaf0f7b28d9c75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v40 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.30.1 (Apple Git-130)
v40-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v40-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 4e8b1d06ce1d5195d6b2b9fe641c609a441a74c4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v40 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.30.1 (Apple Git-130)
v40-0006-nss-Documentation.patchapplication/octet-stream; name=v40-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 760ba3bf701d8a3a0da6507930ce6e7ef1792803 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v40 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 2c31c35a6b..72cc95e05d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 56689ba873..9e4f678b0b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.30.1 (Apple Git-130)
v40-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v40-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From fccd4d4a4cac8ff456d0b68d86082ea378f9a648 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v40 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.30.1 (Apple Git-130)
v40-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v40-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From d419f1e82bab13e926be0cd4dee6f466f146cc6c Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v40 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 9498c18d7d..becd5e0e43 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 4799e927db..197dddbd0b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 34562045cb..3adabcec4d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 1d1282f8dc..4c8fd91fc2 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 8158ea5b2f..57bdc32fb3 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.30.1 (Apple Git-130)
v40-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v40-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 6ad124b8c2313e3df0380b1255acc89201497101 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v40 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v40-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v40-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From a37944ce01a4ef62480b0e883cf516ba05c6fa38 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v40 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.30.1 (Apple Git-130)
v40-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v40-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 60ca4bc07a6235bc5d3da4d709d9b2e47e318c2f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v40 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1197 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3177 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e3ee30f1aa..e66c2cf24a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 8cc23ef7fb..98cfe05d61 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a2e0f8de7e..740ffc7ff6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4398,7 +4398,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4466,6 +4470,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4494,8 +4508,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 49eec3e835..13a231e505 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..0b297d3aed
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1197 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+ const char *host;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension. TODO: if the hostname is a literal IP address we skip
+ * setting it, and check for SNI status in the cert callback. Is this the
+ * correct course of action?
+ */
+ host = conn->connhost[conn->whichhost].host;
+ if (host && host[0] &&
+ !(strspn(host, "0123456789.") == strlen(host) ||
+ strchr(host, ':')))
+ SSL_SetURL(conn->pr_fd, host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* If SNI is enabled we must have a hostname set */
+ if (conn->sslsni && conn->sslsni[0])
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.30.1 (Apple Git-130)
On Tue, 2021-08-10 at 19:22 +0200, Daniel Gustafsson wrote:
Another rebase to work around the recent changes in the ssl Makefile.
I have a local test suite that I've been writing against libpq. With
the new ssldatabase connection option, one tricky aspect is figuring
out whether it's supported or not. It doesn't look like there's any way
to tell, from a client application, whether NSS or OpenSSL (or neither)
is in use.
You'd mentioned that perhaps we should support a call like
PQsslAttribute(NULL, "library"); /* returns "NSS", "OpenSSL", or NULL */
so that you don't have to have an actual connection first in order to
figure out what connection options you need to supply. Clients that
support multiple libpq versions would need to know whether that call is
reliable (older versions of libpq will always return NULL, whether SSL
is compiled in or not), so maybe we could add a feature macro at the
same time?
We could also add a new API (say, PQsslLibrary()) but I don't know if
that gives us anything in practice. Thoughts?
--Jacob
On Wed, Aug 18, 2021 at 12:06:59AM +0000, Jacob Champion wrote:
I have a local test suite that I've been writing against libpq. With
the new ssldatabase connection option, one tricky aspect is figuring
out whether it's supported or not. It doesn't look like there's any way
to tell, from a client application, whether NSS or OpenSSL (or neither)
is in use.
That's about guessing which library libpq is compiled with, so yes
that's a problem.
so that you don't have to have an actual connection first in order to
figure out what connection options you need to supply. Clients that
support multiple libpq versions would need to know whether that call is
reliable (older versions of libpq will always return NULL, whether SSL
is compiled in or not), so maybe we could add a feature macro at the
same time?
Still, the problem is wider than that, no? One cannot know either if
a version of libpq is able to work with GSSAPI until they attempt a
connection with gssencmode. It seems to me that we should work on the
larger picture here.
We could also add a new API (say, PQsslLibrary()) but I don't know if
that gives us anything in practice. Thoughts?
Knowing that the GSSAPI stuff is part of fe-secure.c, we may want
instead a call that returns a list of supported secure libraries.
--
Michael
On 18 Aug 2021, at 02:32, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Aug 18, 2021 at 12:06:59AM +0000, Jacob Champion wrote:
I have a local test suite that I've been writing against libpq. With
the new ssldatabase connection option, one tricky aspect is figuring
out whether it's supported or not. It doesn't look like there's any way
to tell, from a client application, whether NSS or OpenSSL (or neither)
is in use.That's about guessing which library libpq is compiled with, so yes
that's a problem.so that you don't have to have an actual connection first in order to
figure out what connection options you need to supply. Clients that
support multiple libpq versions would need to know whether that call is
reliable (older versions of libpq will always return NULL, whether SSL
is compiled in or not), so maybe we could add a feature macro at the
same time?Still, the problem is wider than that, no? One cannot know either if
a version of libpq is able to work with GSSAPI until they attempt a
connection with gssencmode. It seems to me that we should work on the
larger picture here.
I think we should do both. PQsslAttribute() already exists, and being able to
get the library attribute for NULL conn object when there are multiple
libraries makes a lot of sense to me. That doesn’t exclude working on a better
way for apps to interrogate the libpq they have at hand for which capabilities
it has. Personally I’m not sure what that API could look like, but we should
discuss that in a separate thread I guess.
--
Daniel Gustafsson https://vmware.com/
Attached is a rebased v41 to keep the patch from bitrot.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v41-0006-nss-Documentation.patchapplication/octet-stream; name=v41-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 52f3ef5974073c10f120af71ca49824fadcad873 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v41 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ef0e2a7746..bb231ef316 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v41-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v41-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 0023a98c98ee4fc67380b08729f03abcba8d30d5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v41 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v41-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v41-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 7660cb0b09ce200b19422a1b353a23eebfd40e3d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v41 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1197 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3177 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index e3ee30f1aa..e66c2cf24a 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 78e1ef3e6d..87e5222b44 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c339acf067..eb6cc292ba 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4410,7 +4410,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4478,6 +4482,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4506,8 +4520,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 49eec3e835..13a231e505 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..0b297d3aed
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1197 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+ const char *host;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension. TODO: if the hostname is a literal IP address we skip
+ * setting it, and check for SNI status in the cert callback. Is this the
+ * correct course of action?
+ */
+ host = conn->connhost[conn->whichhost].host;
+ if (host && host[0] &&
+ !(strspn(host, "0123456789.") == strlen(host) ||
+ strchr(host, ':')))
+ SSL_SetURL(conn->pr_fd, host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* If SNI is enabled we must have a hostname set */
+ if (conn->sslsni && conn->sslsni[0])
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not 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 = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v41-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v41-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 03035ced745bc92f630f0c52f8b4ffc732b192b1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v41 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v41-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v41-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 1ef4fc0a20e63a5b309ffd8b9adc803a89cab6bd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v41 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v41-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v41-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 9830765f8f8d025089fcb46edaad56af02a99b9a Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v41 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 9498c18d7d..becd5e0e43 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 4799e927db..197dddbd0b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 34562045cb..3adabcec4d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 1d1282f8dc..4c8fd91fc2 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v41-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v41-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 009458c5ece6bbd196260c975f955a29382a6666 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v41 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v41-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v41-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From eb548735e2e40702ad70123d4151496f9fbbeee5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v41 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v41-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v41-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From abce2ad2a44ce4b62e808fde9e70f4a9ce05b3bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v41 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v41-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v41-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 461f9b69cf1877fc1415b6c9966dc840a0e30c14 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v41 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
Rebased on top of HEAD with off-list comment fixes by Kevin Burke.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v42-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v42-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From ec229040b704aece5573e4490130c57e2ed666c0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v42 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1197 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3177 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a6e4fcc24e..e164350624 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4410,7 +4410,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4478,6 +4482,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4506,8 +4520,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..0a9751c22b
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1197 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+ const char *host;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension. TODO: if the hostname is a literal IP address we skip
+ * setting it, and check for SNI status in the cert callback. Is this the
+ * correct course of action?
+ */
+ host = conn->connhost[conn->whichhost].host;
+ if (host && host[0] &&
+ !(strspn(host, "0123456789.") == strlen(host) ||
+ strchr(host, ':')))
+ SSL_SetURL(conn->pr_fd, host);
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* If SNI is enabled we must have a hostname set */
+ if (conn->sslsni && conn->sslsni[0])
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v42-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v42-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 8c8d64d3e991647c418a0854e536ef2fb145ce48 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v42 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v42-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v42-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 68b4a54defefbcd84ffe1cb27dcc585ecc323f02 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v42 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v42-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v42-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 4ba22a9d83a8df2aa8ea683f7a4ec5315b3a3bf8 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v42 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 9498c18d7d..becd5e0e43 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 4799e927db..197dddbd0b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 2c81a26ac7..dab1e60bbb 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 1d1282f8dc..4c8fd91fc2 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v42-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v42-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 5fe166e6c62e21113ae3a6151e78f4b3561587f7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v42 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v42-0006-nss-Documentation.patchapplication/octet-stream; name=v42-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 87d604c7161ac9e464f1b6f84841cc2fe6db072d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v42 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ef0e2a7746..bb231ef316 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f1cbc1d9e9..04f73532f2 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
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2213,8 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2304,6 +2315,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2378,7 +2401,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2425,6 +2448,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2448,7 +2479,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2552,6 +2583,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v42-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v42-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 06bd65f65c1462cf7c43591fd883164aedf5322f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v42 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v42-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v42-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 723b5216180dd3a498b45af9a9da66a41bd17b54 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v42 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v42-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v42-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 7937976c3b6052f29e0a8bf22ac5504196b06355 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v42 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v42-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v42-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From f1c55d4efe88208c64b54a053696f4becc1833c0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v42 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
On Mon, 2021-07-26 at 15:26 +0200, Daniel Gustafsson wrote:
On 19 Jul 2021, at 21:33, Jacob Champion <pchampion@vmware.com> wrote:
..client connections will crash if
hostaddr is provided rather than host, because SSL_SetURL can't handle
a NULL argument. I'm running with 0002 to fix it for the moment, but
I'm not sure yet if it does the right thing for IP addresses, which the
OpenSSL side has a special case for.AFAICT the idea is to handle it in the cert auth callback, so I've added some
PoC code to check for sslsni there and updated the TODO comment to reflect
that.
I dug a bit deeper into the SNI stuff:
+ server_hostname = SSL_RevealURL(conn->pr_fd); + if (!server_hostname || server_hostname[0] == '\0') + { + /* If SNI is enabled we must have a hostname set */ + if (conn->sslsni && conn->sslsni[0]) + status = SECFailure;
conn->sslsni can be explicitly set to "0" to disable it, so this should
probably be changed to a check for "1", but I'm not sure that would be
correct either. If the user has the default sslsni="1" and supplies an
IP address for the host parameter, I don't think we should fail the
connection.
+ if (host && host[0] && + !(strspn(host, "0123456789.") == strlen(host) || + strchr(host, ':'))) + SSL_SetURL(conn->pr_fd, host);
It looks like NSS may already have some code that prevents SNI from
being sent for IP addresses, so that part of the guard might not be
necessary. (And potentially counterproductive, because it looks like
NSS can perform verification against the certificate's SANs if you pass
an IP address to SSL_SetURL().)
Speaking of IP addresses in SANs, it doesn't look like our OpenSSL
backend can handle those. That's a separate conversation, but I might
take a look at a patch for next commitfest.
--Jacob
On 21 Sep 2021, at 02:06, Jacob Champion <pchampion@vmware.com> wrote:
On Mon, 2021-07-26 at 15:26 +0200, Daniel Gustafsson wrote:
On 19 Jul 2021, at 21:33, Jacob Champion <pchampion@vmware.com> wrote:
..client connections will crash if
hostaddr is provided rather than host, because SSL_SetURL can't handle
a NULL argument. I'm running with 0002 to fix it for the moment, but
I'm not sure yet if it does the right thing for IP addresses, which the
OpenSSL side has a special case for.AFAICT the idea is to handle it in the cert auth callback, so I've added some
PoC code to check for sslsni there and updated the TODO comment to reflect
that.I dug a bit deeper into the SNI stuff:
+ server_hostname = SSL_RevealURL(conn->pr_fd); + if (!server_hostname || server_hostname[0] == '\0') + { + /* If SNI is enabled we must have a hostname set */ + if (conn->sslsni && conn->sslsni[0]) + status = SECFailure;conn->sslsni can be explicitly set to "0" to disable it, so this should
probably be changed to a check for "1",
Agreed.
but I'm not sure that would be
correct either. If the user has the default sslsni="1" and supplies an
IP address for the host parameter, I don't think we should fail the
connection.
Maybe not, but doing so is at least in line with how the OpenSSL support will
handle the same config AFAICT. Or am I missing something?
+ if (host && host[0] && + !(strspn(host, "0123456789.") == strlen(host) || + strchr(host, ':'))) + SSL_SetURL(conn->pr_fd, host);It looks like NSS may already have some code that prevents SNI from
being sent for IP addresses, so that part of the guard might not be
necessary. (And potentially counterproductive, because it looks like
NSS can perform verification against the certificate's SANs if you pass
an IP address to SSL_SetURL().)
Skimming the NSS code I wasn't able find the countermeasures, can you provide a
reference to where I should look?
Feel free to post a new version of the NSS patch with these changes if you want.
Speaking of IP addresses in SANs, it doesn't look like our OpenSSL
backend can handle those. That's a separate conversation, but I might
take a look at a patch for next commitfest.
Please do.
--
Daniel Gustafsson https://vmware.com/
On Mon, 2021-09-27 at 15:44 +0200, Daniel Gustafsson wrote:
On 21 Sep 2021, at 02:06, Jacob Champion <pchampion@vmware.com> wrote:
but I'm not sure that would be
correct either. If the user has the default sslsni="1" and supplies an
IP address for the host parameter, I don't think we should fail the
connection.Maybe not, but doing so is at least in line with how the OpenSSL support will
handle the same config AFAICT. Or am I missing something?
With OpenSSL, I don't see a connection failure when using sslsni=1 with
IP addresses. (verify-full can't work, but that's a separate problem.)
+ if (host && host[0] && + !(strspn(host, "0123456789.") == strlen(host) || + strchr(host, ':'))) + SSL_SetURL(conn->pr_fd, host);It looks like NSS may already have some code that prevents SNI from
being sent for IP addresses, so that part of the guard might not be
necessary. (And potentially counterproductive, because it looks like
NSS can perform verification against the certificate's SANs if you pass
an IP address to SSL_SetURL().)Skimming the NSS code I wasn't able find the countermeasures, can you provide a
reference to where I should look?
I see the check in ssl_ShouldSendSNIExtension(), in ssl3exthandle.c.
Feel free to post a new version of the NSS patch with these changes if you want.
Will do!
Thanks,
--Jacob
On Mon, 2021-09-27 at 16:29 +0000, Jacob Champion wrote:
On Mon, 2021-09-27 at 15:44 +0200, Daniel Gustafsson wrote:
Feel free to post a new version of the NSS patch with these changes if you want.
Will do!
Something like the attached, v43, I think. (since-v42.diff.txt has the
changes only.)
This fixes the interaction of IP addresses and SNI for me, and honors
sslsni=0.
--Jacob
Attachments:
since-v42.diff.txttext/plain; name=since-v42.diff.txtDownload
commit ec0fb068da39236602e8e1de8bbf8487a1093590
Author: Jacob Champion <pchampion@vmware.com>
Date: Mon Sep 27 12:26:29 2021 -0700
squash! nss: Support libnss as TLS library in libpq
Don't send SNI if the user has disabled it. We can remove the IP address
check since NSS does that natively.
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 0a9751c22b..51f09fb7fa 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -183,7 +183,6 @@ pgtls_open_client(PGconn *conn)
PRFileDesc *model;
NSSInitParameters params;
SSLVersionRange desired_range;
- const char *host;
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
@@ -450,15 +449,16 @@ pgtls_open_client(PGconn *conn)
/*
* Specify which hostname we are expecting to talk to for the ClientHello
- * SNI extension. TODO: if the hostname is a literal IP address we skip
- * setting it, and check for SNI status in the cert callback. Is this the
- * correct course of action?
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
*/
- host = conn->connhost[conn->whichhost].host;
- if (host && host[0] &&
- !(strspn(host, "0123456789.") == strlen(host) ||
- strchr(host, ':')))
- SSL_SetURL(conn->pr_fd, host);
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
status = SSL_ForceHandshake(conn->pr_fd);
@@ -678,12 +678,16 @@ pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
CERTGeneralName *san_list;
CERTGeneralName *cn;
- server_hostname = SSL_RevealURL(conn->pr_fd);
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
if (!server_hostname || server_hostname[0] == '\0')
{
- /* If SNI is enabled we must have a hostname set */
- if (conn->sslsni && conn->sslsni[0])
- status = SECFailure;
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
goto done;
}
@@ -762,7 +766,6 @@ done:
PORT_FreeArena(arena, PR_TRUE);
if (server_cert)
CERT_DestroyCertificate(server_cert);
- PR_Free(server_hostname);
if (status == SECSuccess)
return 1;
v43-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchtext/x-patch; name=v43-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchDownload
From d6753e5970e5a610e579c4ca3a5ebc47897f1dc1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v43 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1200 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3180 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..5febe83efb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4422,7 +4422,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4490,6 +4494,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4518,8 +4532,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..51f09fb7fa
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.25.1
v43-0002-Refactor-SSL-testharness-for-multiple-library.patchtext/x-patch; name=v43-0002-Refactor-SSL-testharness-for-multiple-library.patchDownload
From a7d0dff74a03148cceed539e9eb2e0257d734ec4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v43 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.25.1
v43-0003-nss-Add-NSS-specific-tests.patchtext/x-patch; name=v43-0003-nss-Add-NSS-specific-tests.patchDownload
From 310b47cb0c4de313d266f3531852c906fef3b70c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v43 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.25.1
v43-0004-test-check-for-empty-stderr-during-connect_ok.patchtext/x-patch; name=v43-0004-test-check-for-empty-stderr-during-connect_ok.patchDownload
From 6f4474c36886f38247189f5b1bac79222ae6e78d Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v43 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 16570a4e2c..bfea8bb8ae 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index acd379df31..9457277094 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index c484237d07..a5afdc8680 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index f670bc5e0d..d7f49739dd 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.25.1
v43-0005-nss-pg_strong_random-support.patchtext/x-patch; name=v43-0005-nss-pg_strong_random-support.patchDownload
From eca6452fa47df64eeec7c9c38ce996e832ba233b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v43 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.25.1
v43-0006-nss-Documentation.patchtext/x-patch; name=v43-0006-nss-Documentation.patchDownload
From 2a75e981d45bfdb7a5e56ea77b9d3eb43a83ca96 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v43 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..fd1ba4d979 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.25.1
v43-0007-nss-Support-NSS-in-pgcrypto.patchtext/x-patch; name=v43-0007-nss-Support-NSS-in-pgcrypto.patchDownload
From 1e9fc1c7003d17608d1307d69f46af5bed1b9075 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v43 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.25.1
v43-0008-nss-Support-NSS-in-sslinfo.patchtext/x-patch; name=v43-0008-nss-Support-NSS-in-sslinfo.patchDownload
From db7f1345edab0458269923767077adb90d5a9e2e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v43 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.25.1
v43-0009-nss-Support-NSS-in-cryptohash.patchtext/x-patch; name=v43-0009-nss-Support-NSS-in-cryptohash.patchDownload
From e37c36ce077fb234a08aa17f21eaf0c4a1731ceb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v43 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.25.1
v43-0010-nss-Build-infrastructure.patchtext/x-patch; name=v43-0010-nss-Build-infrastructure.patchDownload
From 4dc2ae68e3a3fd9cb5ba6398362e8b518e4713e1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v43 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.25.1
On Mon, Sep 20, 2021 at 2:38 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Rebased on top of HEAD with off-list comment fixes by Kevin Burke.
Hello Daniel,
I've been playing with your patch on Mac (OS 11.6 Big Sur) and have
run into a couple of issues so far.
1. I get 7 warnings while running make (truncated):
cryptohash_nss.c:101:21: warning: implicit conversion from enumeration
type 'SECOidTag' to different enumeration type 'HASH_HashType'
[-Wenum-conversion]
ctx->hash_type = SEC_OID_SHA1;
~ ^~~~~~~~~~~~
...
cryptohash_nss.c:134:34: warning: implicit conversion from enumeration
type 'HASH_HashType' to different enumeration type 'SECOidTag'
[-Wenum-conversion]
hash = SECOID_FindOIDByTag(ctx->hash_type);
~~~~~~~~~~~~~~~~~~~ ~~~~~^~~~~~~~~
7 warnings generated.
2. libpq-refs-stamp fails -- it appears an exit is being injected into
libpq on Mac
Notes about my environment:
I've installed nss via homebrew (at version 3.70) and linked it.
Cheers,
Rachel
On 28 Sep 2021, at 01:07, Rachel Heaton <rachelmheaton@gmail.com> wrote:
1. I get 7 warnings while running make (truncated):
cryptohash_nss.c:101:21: warning: implicit conversion from enumeration
type 'SECOidTag' to different enumeration type 'HASH_HashType'
Nice catch, fixed in the attached.
2. libpq-refs-stamp fails -- it appears an exit is being injected into
libpq on Mac
I spent some time investigating this, and there are two cases of _exit() and
one atexit() which are coming from the threading code in libnspr (which is the
runtime lib required by libnss).
On macOS the threading code registers an atexit handler [0]https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1034 in order to work
around issues with __attribute__((destructor)) [1]https://bugzilla.mozilla.org/show_bug.cgi?id=1399746#c99. The pthreads code also
defines PR_ProcessExit [2]https://www-archive.mozilla.org/projects/nspr/reference/html/prinit.html#15859 which does what it says on the tin, calls exit and
not much more [3]https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1181. Both of these uses are only compiled when building with
pthreads, which can be disabled in autoconf but that seems broken in recent
version of NSPR. I'm fairly sure I've built NSPR with the user pthreads in the
past, but if packagers build it like this then we need to conform to that. The
PR_CreateProcess() [4]https://www-archive.mozilla.org/projects/nspr/reference/html/prprocess.html#24535 call further calls _exit() [5]https://hg.mozilla.org/projects/nspr/file/tip/pr/src/md/unix/uxproces.c#l268 in a number of error
paths on failing syscalls.
The libpq libnss implementation doesn't call either of these, and neither does
libnss.
I'm not entirely sure what to do here, it clearly requires an exception in the
Makefile check of sorts if we deem we can live with this.
@Jacob: how did you configure your copy of NSPR?
--
Daniel Gustafsson https://vmware.com/
[0]: https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1034
[1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1399746#c99
[2]: https://www-archive.mozilla.org/projects/nspr/reference/html/prinit.html#15859
[3]: https://hg.mozilla.org/projects/nspr/file/tip/pr/src/pthreads/ptthread.c#l1181
[4]: https://www-archive.mozilla.org/projects/nspr/reference/html/prprocess.html#24535
[5]: https://hg.mozilla.org/projects/nspr/file/tip/pr/src/md/unix/uxproces.c#l268
Attachments:
v44-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v44-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 493069fccfd87ef4a6d823680634a2ad0dab3b1c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v44 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 7cbdeb589b..1fe6155e07 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 8c901a5beb0d1063f8a8f20fadb76f5467b29741 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v44 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1200 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3180 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..5febe83efb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4422,7 +4422,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4490,6 +4494,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4518,8 +4532,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..e8225a4d9d
--- /dev/null
+++ b/src/common/protocol_nss.c
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with protocols
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/protocol_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+/*
+ * ssl_protocol_version_to_string
+ * Translate NSS TLS version to string
+ */
+char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+#endif
+ }
+
+ return pstrdup("unknown");
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..51f09fb7fa
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ ¶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 = 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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v44-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v44-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 173177aa000abad6f82ca76a5b8fc03865578dd7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v44 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v44-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v44-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 2103ccfaeabf4c68e43eb3cf7c473c73f7316213 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v44 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v44-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v44-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 9482c136f352d0c6420f1c352025759c0ac141ad Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v44 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 16570a4e2c..bfea8bb8ae 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index acd379df31..9457277094 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index c484237d07..a5afdc8680 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index f670bc5e0d..d7f49739dd 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v44-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v44-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From dfda535956a1b7b5a73bf5157bfa57eb02ed4416 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v44 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v44-0006-nss-Documentation.patchapplication/octet-stream; name=v44-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 3fd458bd5c716ef249ebfb4354ff5594714ada75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v44 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..fd1ba4d979 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v44-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v44-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 9abf0fc21cac45603caa0d3d488fad73194eca02 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v44 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v44-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v44-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 86ce7b7fc6c7e0ec5a2f91a8d007e2bfd345623f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v44 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v44-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v44-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 619abeb421370fee1e62c018a39a2396b8f17fd9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v44 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..b90aed83f1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
On Thu, 2021-09-30 at 14:17 +0200, Daniel Gustafsson wrote:
The libpq libnss implementation doesn't call either of these, and neither does
libnss.
I thought the refs check only searched for direct symbol dependencies;
is that piece of NSPR being statically included somehow?
I'm not entirely sure what to do here, it clearly requires an exception in the
Makefile check of sorts if we deem we can live with this.@Jacob: how did you configure your copy of NSPR?
I use the Ubuntu 20.04 builtin (NSPR 4.25.0), but it looks like the
reason I haven't been seeing this is because I've always used --enable-
coverage. If I take that out, I see the same exit check failure.
--Jacob
On Thu, 2021-09-30 at 16:04 +0000, Jacob Champion wrote:
On Thu, 2021-09-30 at 14:17 +0200, Daniel Gustafsson wrote:
The libpq libnss implementation doesn't call either of these, and neither does
libnss.I thought the refs check only searched for direct symbol dependencies;
is that piece of NSPR being statically included somehow?
On my machine, at least, exit() is coming in due to a few calls to
psprintf(), pstrdup(), and pg_malloc() in the new NSS code.
(Disassembly via `objdump -S libpq.so` helped me track those down.) I'm
working on a patch.
--Jacob
On 1 Oct 2021, at 02:02, Jacob Champion <pchampion@vmware.com> wrote:
On my machine, at least, exit() is coming in due to a few calls to
psprintf(), pstrdup(), and pg_malloc() in the new NSS code.
(Disassembly via `objdump -S libpq.so` helped me track those down.) I'm
working on a patch.
Ah, that makes perfect sense. I was too focused on hunting in what new was
linked against that I overlooked the obvious. Thanks for finding these.
--
Daniel Gustafsson https://vmware.com/
On Fri, 2021-10-01 at 08:55 +0200, Daniel Gustafsson wrote:
Ah, that makes perfect sense. I was too focused on hunting in what new was
linked against that I overlooked the obvious. Thanks for finding these.
No problem at all :) The exit() check is useful but still a little
opaque, I think, especially since (from my newbie perspective) there's
so much of the pgcommon staticlib that is forbidden for use in libpq.
Fixed in v44, attached; changes in since-v43.diff.txt.
--Jacob
Attachments:
since-v43.diff.txttext/plain; name=since-v43.diff.txtDownload
commit 5a9783fc2fdd9ea8723d2cd7c3737b854c12fc25
Author: Jacob Champion <pchampion@vmware.com>
Date: Mon Oct 4 08:59:46 2021 -0700
squash! nss: Support libnss as TLS library in libpq
Don't use palloc() in front-end code.
- ssl_protocol_version_to_string() returns static allocations for the
OpenSSL implementation, so do the same here.
- Use PQExpBuffers for sprintf-style allocation.
- Use plain malloc() rather than pg_malloc().
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
index e8225a4d9d..4d12c9f388 100644
--- a/src/common/protocol_nss.c
+++ b/src/common/protocol_nss.c
@@ -39,21 +39,21 @@ ssl_protocol_version_to_string(int v)
break;
case SSL_LIBRARY_VERSION_TLS_1_0:
- return pstrdup("TLSv1.0");
+ return "TLSv1.0";
#ifdef SSL_LIBRARY_VERSION_TLS_1_1
case SSL_LIBRARY_VERSION_TLS_1_1:
- return pstrdup("TLSv1.1");
+ return "TLSv1.1";
#endif
#ifdef SSL_LIBRARY_VERSION_TLS_1_2
case SSL_LIBRARY_VERSION_TLS_1_2:
- return pstrdup("TLSv1.2");
+ return "TLSv1.2";
#endif
#ifdef SSL_LIBRARY_VERSION_TLS_1_3
case SSL_LIBRARY_VERSION_TLS_1_3:
- return pstrdup("TLSv1.3");
+ return "TLSv1.3";
#endif
}
- return pstrdup("unknown");
+ return "unknown";
}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
index 51f09fb7fa..e1574997ee 100644
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -229,12 +229,22 @@ pgtls_open_client(PGconn *conn)
if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
{
- char *ssldatabase_path = psprintf("sql:%s", conn->ssldatabase);
+ PQExpBufferData ssldatabase_path;
- conn->nss_context = NSS_InitContext(ssldatabase_path, "", "", "",
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
¶ms,
NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
- pfree(ssldatabase_path);
+ termPQExpBuffer(&ssldatabase_path);
if (!conn->nss_context)
{
@@ -647,7 +657,14 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
goto cleanup;
}
- ret = pg_malloc(digest.len);
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
memcpy(ret, digest.data, digest.len);
*len = digest_len;
@@ -799,9 +816,15 @@ static SECStatus
pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
{
SECMODModule *mod;
- char *modulespec;
+ PQExpBufferData modulespec;
- modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
/*
* Attempt to load the specified module. The second parameter is "parent"
@@ -811,8 +834,8 @@ pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
* defined in the modulespec, and since we don't support anything but
* directly addressed modules we should pass PR_FALSE.
*/
- mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
- pfree(modulespec);
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
if (mod && mod->loaded)
{
v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchtext/x-patch; name=v44-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchDownload
From e8e8a2696e4814e90655ea83076cae6694930d4f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v44 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1223 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3203 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..5febe83efb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4422,7 +4422,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4490,6 +4494,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4518,8 +4532,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..e1574997ee
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.25.1
v44-0002-Refactor-SSL-testharness-for-multiple-library.patchtext/x-patch; name=v44-0002-Refactor-SSL-testharness-for-multiple-library.patchDownload
From 0208f2c2cdcae8eec53acc947f45b0090db34cf2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v44 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.25.1
v44-0003-nss-Add-NSS-specific-tests.patchtext/x-patch; name=v44-0003-nss-Add-NSS-specific-tests.patchDownload
From 18e3b2b2d175d44baa06299d2d721782ed9b7480 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v44 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.25.1
v44-0004-test-check-for-empty-stderr-during-connect_ok.patchtext/x-patch; name=v44-0004-test-check-for-empty-stderr-during-connect_ok.patchDownload
From a563dc0cc91220edea03caae99c9fcb252979649 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v44 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 16570a4e2c..bfea8bb8ae 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index acd379df31..9457277094 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index c484237d07..a5afdc8680 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index f670bc5e0d..d7f49739dd 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.25.1
v44-0005-nss-pg_strong_random-support.patchtext/x-patch; name=v44-0005-nss-pg_strong_random-support.patchDownload
From f7c4c55b504448c04448360f0d5d65763abaca15 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v44 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.25.1
v44-0006-nss-Documentation.patchtext/x-patch; name=v44-0006-nss-Documentation.patchDownload
From 5d33e163538c8b496afbc70848f7dbad8a47904e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v44 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..fd1ba4d979 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.25.1
v44-0007-nss-Support-NSS-in-pgcrypto.patchtext/x-patch; name=v44-0007-nss-Support-NSS-in-pgcrypto.patchDownload
From c62658ee8b4c3e091c1e68b5063bf44c2bb2b841 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v44 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.25.1
v44-0008-nss-Support-NSS-in-sslinfo.patchtext/x-patch; name=v44-0008-nss-Support-NSS-in-sslinfo.patchDownload
From 24135e6d9e68131f3600935bf366b3eab6dd5829 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v44 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.25.1
v44-0009-nss-Support-NSS-in-cryptohash.patchtext/x-patch; name=v44-0009-nss-Support-NSS-in-cryptohash.patchDownload
From a28fb8e265283ab7a3923297e623445ab9cd2891 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v44 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..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 <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ 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 = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.25.1
v44-0010-nss-Build-infrastructure.patchtext/x-patch; name=v44-0010-nss-Build-infrastructure.patchDownload
From 8c391f4a85fd0cd0c2e28d588e89298de838fdd1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v44 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d98bc07143..bf93498df1 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.25.1
On 4 Oct 2021, at 18:14, Jacob Champion <pchampion@vmware.com> wrote:
On Fri, 2021-10-01 at 08:55 +0200, Daniel Gustafsson wrote:
Ah, that makes perfect sense. I was too focused on hunting in what new was
linked against that I overlooked the obvious. Thanks for finding these.No problem at all :) The exit() check is useful but still a little
opaque, I think, especially since (from my newbie perspective) there's
so much of the pgcommon staticlib that is forbidden for use in libpq.
Thanks! These changes looks good. Since you accidentally based this on v43
and not the v44 I posted with the cryptohash fix in, the attached is a v45 with
both your v44 and the previous one, all rebased over HEAD.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v45-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v45-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From e24e0051c7a699530354aecb7a2d8d59255a65c4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v45 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1223 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3203 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c7b7db8065..6d63528954 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9449,7 +9449,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d2ce4a8450..5febe83efb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4422,7 +4422,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4490,6 +4494,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4518,8 +4532,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..e1574997ee
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v45-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v45-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From d61b1d39868fa3946e2fbc4ffff8391fec29f2b2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v45 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 3bc711f4a7..35184cd80c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -7,12 +7,10 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -35,32 +33,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -75,32 +47,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -586,7 +548,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1dfa2b91f3..e05e60f092 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 804d008245..33bf351476 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v45-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v45-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From d04ea41b073ef624840fba0dec95d95bb21ebbb1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v45 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 246 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 395 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 ++-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 46 +--
6 files changed, 610 insertions(+), 190 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index c216560dcc..4628b24908 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -33,6 +33,37 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
SSLDIRS := ssl/client-crldir ssl/server-crldir \
ssl/root+client-crldir ssl/root+server-crldir
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
# This target re-generates all the key and certificate files. Usually we just
# use the ones that are committed to the tree without rebuilding them.
#
@@ -40,6 +71,10 @@ SSLDIRS := ssl/client-crldir ssl/server-crldir \
#
sslfiles: $(SSLFILES) $(SSLDIRS)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -67,6 +102,35 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir
rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config
@@ -74,6 +138,87 @@ ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl x509 -in ssl/temp.crt -out ssl/server-$*.crt # to keep just the PEM cert
rm ssl/server-$*.csr
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -92,6 +237,27 @@ ssl/client-dn.crt: ssl/client-dn.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client-dn.crt # to keep just the PEM cert
rm ssl/client-dn.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config
@@ -99,6 +265,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
# Convert the key to DER, to test our behaviour there too
ssl/client-der.key: ssl/client.key
openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
@@ -131,19 +305,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -169,14 +364,65 @@ ssl/client-crldir: ssl/client.crl
mkdir ssl/client-crldir
cp ssl/client.crl ssl/client-crldir/`openssl crl -hash -noout -in ssl/client.crl`.r0
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
.PHONY: sslfiles-clean
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt
rm -rf $(SSLDIRS)
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 35184cd80c..f961ba9737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -12,13 +12,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -54,19 +63,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -94,7 +152,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -113,87 +171,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -204,14 +279,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -226,21 +301,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -250,21 +326,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -274,43 +350,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -328,29 +404,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -364,62 +457,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -457,18 +570,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -483,16 +597,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -500,7 +618,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -525,9 +643,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -535,17 +653,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index e05e60f092..2bfe0b3573 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 33bf351476..4972835bc4 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v45-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v45-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 678606499a83c9787c708bba7a676096dc9b0062 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v45 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgresNode.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 16570a4e2c..bfea8bb8ae 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index acd379df31..9457277094 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index c484237d07..a5afdc8680 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index f670bc5e0d..d7f49739dd 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c59da758c7..01762be2ff 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2055,8 +2055,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = TestLib::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f961ba9737..dc21ae9d1e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -18,12 +18,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 2bfe0b3573..0dd286b339 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v45-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v45-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 4ca89485e3bf8388d3b2b583e4851817c54e7424 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v45 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v45-0006-nss-Documentation.patchapplication/octet-stream; name=v45-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 5140d4395304b08b5c789e54f3f63ac3dfdb2ca8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v45 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 31 ++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 494 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0a8e35c59f..fd1ba4d979 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 1ebe8482bf..6dab945e07 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,31 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed. Additionally,
+ <productname>NSS</productname> requires <productname>NSPR</productname>
+ to be installed. <filename>configure</filename> will check for the
+ required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v45-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v45-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From efbeebdb1934a2aa54cdefaafec3de8602969175 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v45 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v45-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v45-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 7f6d55bef98e1422b24f5379ec6074ed20c17276 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v45 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v45-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v45-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 0be8031cbe579784a9cfe2f4b68cbaf9abd02f9f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v45 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..b90aed83f1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v45-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v45-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 1c8ec7921dc9ea2980a1b36644c2db1f21cd2c9b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v45 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 294 +++++++++++++++++++++++++++++++++-
configure.ac | 31 +++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 ++++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 411 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 7542fe30a1..3c984b0927 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,274 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +13949,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18825,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18857,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index ed3cdb9a8e..68fcc8d4e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,28 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1479,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2274,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2285,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 165a93987a..e2d0cfb1da 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -559,6 +563,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1017,6 +1028,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
On Tue, 2021-10-05 at 15:08 +0200, Daniel Gustafsson wrote:
Thanks! These changes looks good. Since you accidentally based this on v43
and not the v44 I posted with the cryptohash fix in, the attached is a v45 with
both your v44 and the previous one, all rebased over HEAD.
Thanks, and sorry about that.
--Jacob
Hi all, apologies but I'm having trouble applying the latest patch (v45) to
the latest commit on master (6b0f6f79eef2168ce38a8ee99c3ed76e3df5d7ad)
I downloaded all of the patches to my local filesystem, and then ran:
for patch in
../../kevinburke/rustls-postgres/patchsets/2021-10-05-gustafsson-mailing-list/*.patch;
do git am $patch; done;
I get the following error on the second patch file:
Applying: Refactor SSL testharness for multiple library
error: patch failed: src/test/ssl/t/001_ssltests.pl:7
error: src/test/ssl/t/001_ssltests.pl: patch does not apply
error: patch failed: src/test/ssl/t/SSLServer.pm:26
error: src/test/ssl/t/SSLServer.pm: patch does not apply
Patch failed at 0001 Refactor SSL testharness for multiple library
hint: Use 'git am --show-current-patch=diff' to see the failed patch
I believe that these patches need to integrate the refactoring in
commit b3b4d8e68ae83f432f43f035c7eb481ef93e1583 - git is searching for the
wrong text in the existing file, but I'm not sure how to submit a patch
against a patch.
Thanks,
Kevin
On Tue, Oct 5, 2021 at 8:05 AM Jacob Champion <pchampion@vmware.com> wrote:
Show quoted text
On Tue, 2021-10-05 at 15:08 +0200, Daniel Gustafsson wrote:
Thanks! These changes looks good. Since you accidentally based this on
v43
and not the v44 I posted with the cryptohash fix in, the attached is a
v45 with
both your v44 and the previous one, all rebased over HEAD.
Thanks, and sorry about that.
--Jacob
For anyone else trying to test out this branch I'm able to get the patches
to apply cleanly if I check out e.g. commit
92e6a98c3636948e7ece9a3260f9d89dd60da278.
Kevin
--
Kevin Burke
phone: 925-271-7005 | kevin.burke.dev
On Thu, Oct 28, 2021 at 9:31 PM Kevin Burke <kevin@burke.dev> wrote:
Show quoted text
Hi all, apologies but I'm having trouble applying the latest patch (v45)
to the latest commit on master (6b0f6f79eef2168ce38a8ee99c3ed76e3df5d7ad)I downloaded all of the patches to my local filesystem, and then ran:
for patch in
../../kevinburke/rustls-postgres/patchsets/2021-10-05-gustafsson-mailing-list/*.patch;
do git am $patch; done;I get the following error on the second patch file:
Applying: Refactor SSL testharness for multiple library
error: patch failed: src/test/ssl/t/001_ssltests.pl:7
error: src/test/ssl/t/001_ssltests.pl: patch does not apply
error: patch failed: src/test/ssl/t/SSLServer.pm:26
error: src/test/ssl/t/SSLServer.pm: patch does not apply
Patch failed at 0001 Refactor SSL testharness for multiple library
hint: Use 'git am --show-current-patch=diff' to see the failed patchI believe that these patches need to integrate the refactoring in
commit b3b4d8e68ae83f432f43f035c7eb481ef93e1583 - git is searching for the
wrong text in the existing file, but I'm not sure how to submit a patch
against a patch.Thanks,
KevinOn Tue, Oct 5, 2021 at 8:05 AM Jacob Champion <pchampion@vmware.com>
wrote:On Tue, 2021-10-05 at 15:08 +0200, Daniel Gustafsson wrote:
Thanks! These changes looks good. Since you accidentally based this
on v43
and not the v44 I posted with the cryptohash fix in, the attached is a
v45 with
both your v44 and the previous one, all rebased over HEAD.
Thanks, and sorry about that.
--Jacob
On 29 Oct 2021, at 06:31, Kevin Burke <kevin@burke.dev> wrote:
Thanks for testing the patch!
I believe that these patches need to integrate the refactoring in commit
b3b4d8e68ae83f432f43f035c7eb481ef93e1583 - git is searching for the wrong text
in the existing file
Correct, b3b4d8e68 as well as b4c4a00ea both created conflicts with this
patchset. Attached is an updated patchset fixing both of those as well as
adding version checks for NSS and NSPR to autoconf (with fallbacks for
non-{nss|nspr}-config systems). The versions picked are semi-arbitrary and
definitely up for discussion. I chose them mainly as they were the oldest
commonly available packages I found, and they satisfy the requirements we have.
I'm not sure how to submit a patch against a patch.
If you've done the work of fixing the conflicts in a rebase, the best option is
IMO to supply a whole new version of the patchset since that will make the CF
patch tester be able to build and test the version.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v46-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v46-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 43d5880e4efc5b0acf931765c237c37ca0ed6fc1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v46 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1223 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3203 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index fd141a0fa5..a3f45fcef6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9452,7 +9452,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..3c7317eac8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4435,7 +4435,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4503,6 +4507,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4531,8 +4545,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..e1574997ee
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v46-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v46-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From cc7db1f0c8f4f309458818527a7b3b6838ba9d7f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v46 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2f30d11763..d965fbb658 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,32 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -76,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -613,7 +575,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 983554263f..59f520cb5b 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c5999e0b33..83df239e20 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v46-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v46-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From d7e0b144575cda182926a28bfd738463c4de2936 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v46 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 432 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +--
8 files changed, 664 insertions(+), 212 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index cb24e31c6e..d3d007d1fb 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -16,14 +16,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 81c20aac9e..e505f8552d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d965fbb658..2fec9143c5 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -365,62 +458,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -449,30 +562,33 @@ TODO:
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -484,18 +600,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -510,16 +627,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -527,7 +648,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -552,9 +673,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -562,17 +683,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 59f520cb5b..f6a0a23390 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 83df239e20..68cfd53dc3 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -145,7 +144,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v46-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v46-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From b6e25f174cdce178118d1a5831686125510c58f4 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v46 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 11ca525c6c..f749633ca8 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 766fed67cc..8b8fad7c86 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 13d664dda0..28a6f44250 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..523a77b2c5 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 9467a199c8..2798ca8dd6 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2142,8 +2142,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2fec9143c5..8daf499a35 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index f6a0a23390..9fdc7989ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v46-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v46-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 028fe80bba70253169d0738e8970e68fe0812f85 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v46 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v46-0006-nss-Documentation.patchapplication/octet-stream; name=v46-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 8efbc9099667dab31eea3dfb4d61e307175231d8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v46 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index de77f14573..aa8cc2c8bb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..fdf198729e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <applications>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index d74d1ed7af..6318daa07f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v46-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v46-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 4da12983e1d0b9e930dc0cff2e026c149d35cb75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v46 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index bbaa2691fe..3cbe41fb9a 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v46-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v46-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 81888434896893b7f87298beec7e4392cf75d756 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v46 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v46-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v46-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From e213312fac673cc704e846e3fd73dd3f4ed9b66d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v46 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..b90aed83f1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v46-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v46-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 703321053f7854994bc830a4e5bef1ae652d1aa7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v46 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 375 +++++++++++++++++++++++++++++++++-
configure.ac | 80 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 +++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 541 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 4ffefe4655..51d6f493f4 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,355 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +14030,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18906,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18938,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 44ee3ebe2f..998f5ba170 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,77 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1528,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2323,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2334,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 43fd1be088..313d38a95b 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -560,6 +564,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1019,6 +1030,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
Attached is a rebase fixing a tiny bug in the documentation which prevented it
from being able to compile.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v47-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v47-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From de55411fee5a32db8f288419120bbac5afedf1de Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v47 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2f30d11763..d965fbb658 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,32 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -76,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -613,7 +575,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 983554263f..59f520cb5b 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c5999e0b33..83df239e20 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v47-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v47-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 0e1be5e7f33be8ddc1c64524b2040fbd32214913 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v47 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 375 +++++++++++++++++++++++++++++++++-
configure.ac | 80 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 40 +++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 541 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 4ffefe4655..51d6f493f4 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,355 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +14030,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18540,6 +18906,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18569,7 +18938,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 44ee3ebe2f..998f5ba170 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,77 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1528,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2323,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2334,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 15ffdd895a..273b7f63d4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -340,6 +340,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -352,6 +358,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -926,6 +935,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4362bd44fd..7dc45c399e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -438,9 +458,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
@@ -470,6 +495,11 @@ sub mkvcbuild
$pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c',
'pgp-mpi-openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c',
+ 'pgp-mpi-internal.c', 'imath.c', 'blf.c');
+ }
else
{
$pgcrypto->AddFiles(
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 43fd1be088..313d38a95b 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,10 +307,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -499,6 +502,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -560,6 +564,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1019,6 +1030,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v47-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v47-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 98533ab685c0e998df5fbfd0ac7e818d240daeab Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v47 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..b90aed83f1
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v47-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v47-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 5e493bf3f15ef05358b8c37028fa9be230611356 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v47 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 30cae0bb98..3aadd90aa6 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,6 +23,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -31,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -131,6 +135,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -282,7 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -338,6 +359,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -471,3 +493,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v47-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v47-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 8f064480efc8ee7956271a856318055012fd7c2c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v47 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 7 +-
contrib/pgcrypto/nss.c | 753 +++++++++++++++++++++++++++++++++++++
doc/src/sgml/pgcrypto.sgml | 49 ++-
3 files changed, 795 insertions(+), 14 deletions(-)
create mode 100644 contrib/pgcrypto/nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -7,11 +7,14 @@ INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
+NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c
+NSS_TESTS = sha2 des 3des
+
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_TESTS))
+CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS)))
+CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS)))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = \
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "px.h"
+#include "blf.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * Define our own mechanisms for Blowfish as it's not implemented by NSS.
+ */
+#define BLOWFISH_CBC (1)
+#define BLOWFISH_ECB (2)
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ union
+ {
+ CK_MECHANISM_TYPE nss;
+ CK_ULONG internal;
+ } mechanism;
+ int keylen;
+ bool is_nss;
+} cipher_implementation;
+
+typedef struct nss_cipher
+{
+ const cipher_implementation *impl;
+ NSSInitContext *context;
+ PK11Context *crypt_context;
+ SECItem *params;
+
+ PK11SymKey *encrypt_key;
+ PK11SymKey *decrypt_key;
+} nss_cipher;
+
+typedef struct internal_cipher
+{
+ const cipher_implementation *impl;
+ BlowfishContext context;
+} internal_cipher;
+
+typedef struct nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ blowfish_setkey(&cipher->context, key, klen);
+ if (iv)
+ blowfish_setiv(&cipher->context, iv);
+
+ return 0;
+}
+
+static int
+bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+ uint8 *buf;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ buf = palloc(dlen);
+ memcpy(buf, data, dlen);
+
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_encrypt_ecb(buf, dlen, &cipher->context);
+ else
+ blowfish_encrypt_cbc(buf, dlen, &cipher->context);
+
+ memcpy(res, buf, dlen);
+ pfree(buf);
+
+ return 0;
+}
+
+static int
+bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ if (dlen == 0)
+ return 0;
+
+ if (dlen & 7)
+ return PXE_NOTBLOCKSIZE;
+
+ memcpy(res, data, dlen);
+ if (cipher->impl->mechanism.internal == BLOWFISH_ECB)
+ blowfish_decrypt_ecb(res, dlen, &cipher->context);
+ else
+ blowfish_decrypt_cbc(res, dlen, &cipher->context);
+
+ return 0;
+}
+
+static void
+bf_free(PX_Cipher *pxc)
+{
+ internal_cipher *cipher = (internal_cipher *) pxc->ptr;
+
+ pfree(cipher);
+ pfree(pxc);
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism.nss == CKM_AES_CBC ||
+ cipher->impl->mechanism.nss == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism.nss);
+}
+
+static unsigned
+bf_get_block_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+static unsigned
+bf_get_key_size(PX_Cipher *pxc)
+{
+ return 448 / 8;
+}
+
+static unsigned
+bf_get_iv_size(PX_Cipher *pxc)
+{
+ return 8;
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_CBC,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES_ECB,
+ .keylen = 8,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_CBC,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_DES3_ECB,
+ .keylen = 24,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_CBC,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism.nss = CKM_AES_ECB,
+ .keylen = 32,
+ .is_nss = true
+};
+
+static const cipher_implementation nss_bf_ecb = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_ECB,
+ .keylen = 56,
+ .is_nss = false
+};
+
+static const cipher_implementation nss_bf_cbc = {
+ .init = bf_init,
+ .get_block_size = bf_get_block_size,
+ .get_key_size = bf_get_key_size,
+ .get_iv_size = bf_get_iv_size,
+ .encrypt = bf_encrypt,
+ .decrypt = bf_decrypt,
+ .free = bf_free,
+ .mechanism.internal = BLOWFISH_CBC,
+ .keylen = 56,
+ .is_nss = false
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal
+ * implementation while CAST5 isn't supported at all at this time,
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {"blowfish", &nss_bf_cbc},
+ {"blowfish-cbc", &nss_bf_cbc},
+ {"blowfish-ecb", &nss_bf_ecb},
+ {"bf", &nss_bf_cbc},
+ {"bf-cbc", &nss_bf_cbc},
+ {"bf-ecb", &nss_bf_ecb},
+ {NULL}
+};
+
+/*
+ * px_find_cipher
+ *
+ * Search for the requested cipher and see if there is support for it, and if
+ * so return an allocated object containing the playbook for how to encrypt
+ * and decrypt using the cipher.
+ */
+int
+px_find_cipher(const char *alias, PX_Cipher **res)
+{
+ const nss_cipher_ref *cipher_ref;
+ PX_Cipher *px_cipher;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ nss_cipher *cipher;
+ internal_cipher *int_cipher;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * Set the private data, which is different for NSS and internal ciphers
+ */
+ if (cipher_ref->impl->is_nss)
+ {
+ /*
+ * Initialize our own NSS context without a database backing it. At
+ * some point we might want to allow users to use stored keys in the
+ * database rather than passing them via SELECT encrypt(), and then
+ * this need to be changed to open a user specified database.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+ }
+ else
+ {
+ int_cipher = palloc0(sizeof(*int_cipher));
+
+ int_cipher->impl = cipher_ref->impl;
+ px_cipher->ptr = int_cipher;
+ }
+
+ *res = px_cipher;
+ return 0;
+}
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 648703cbc7..7c2e2813f9 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
<literal>sha224</literal>, <literal>sha256</literal>,
<literal>sha384</literal> and <literal>sha512</literal>.
If <filename>pgcrypto</filename> was built with
- <productname>OpenSSL</productname>, more algorithms are available, as
- detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+ <productname>OpenSSL</productname> or <productname>NSS</productname>,
+ more algorithms are available, as
+ detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
</para>
<para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
Which cipher algorithm to use.
</para>
<literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
Default: aes128
Applies to: pgp_sym_encrypt, pgp_pub_encrypt
</literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
BIGNUM functions.
</para>
- <table id="pgcrypto-with-without-openssl">
- <title>Summary of Functionality with and without OpenSSL</title>
- <tgroup cols="3">
+ <table id="pgcrypto-with-without-openssl-nss">
+ <title>Summary of Functionality with and without external cryptographic
+ library</title>
+ <tgroup cols="4">
<thead>
<row>
<entry>Functionality</entry>
<entry>Built-in</entry>
<entry>With OpenSSL</entry>
+ <entry>With NSS</entry>
</row>
</thead>
<tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
<entry>MD5</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA1</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>SHA224/256/384/512</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>Other digest algorithms</entry>
<entry>no</entry>
<entry>yes (Note 1)</entry>
+ <entry>yes (Note 1)</entry>
</row>
<row>
<entry>Blowfish</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes (Note 2)</entry>
</row>
<row>
<entry>AES</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
- <entry>DES/3DES/CAST5</entry>
+ <entry>DES/3DES</entry>
<entry>no</entry>
<entry>yes</entry>
+ <entry>yes</entry>
+ </row>
+ <row>
+ <entry>CAST5</entry>
+ <entry>no</entry>
+ <entry>yes</entry>
+ <entry>no</entry>
</row>
<row>
<entry>Raw encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Symmetric encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
<row>
<entry>PGP Public-Key encryption</entry>
<entry>yes</entry>
<entry>yes</entry>
+ <entry>yes</entry>
</row>
</tbody>
</tgroup>
@@ -1248,12 +1267,18 @@ gen_random_uuid() returns uuid
<orderedlist>
<listitem>
<para>
- Any digest algorithm <productname>OpenSSL</productname> supports
- is automatically picked up.
- This is not possible with ciphers, which need to be supported
+ Any digest algorithm included with the library that
+ <productname>PostgreSQL</productname> is compiled with is automatically
+ picked up. This is not possible with ciphers, which need to be supported
explicitly.
</para>
</listitem>
+ <listitem>
+ <para>
+ Blowfish is not supported by <productname>NSS</productname>, the
+ built-in implementation is used as a fallback.
+ </para>
+ </listitem>
</orderedlist>
</sect3>
--
2.24.3 (Apple Git-128)
v47-0006-nss-Documentation.patchapplication/octet-stream; name=v47-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 1e9ef102ad69d8bee5863d90909a55665c692b5f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v47 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index de77f14573..aa8cc2c8bb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..db44c415d0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b449c834a9..5dbfe9f98c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8708,6 +8881,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8734,6 +8913,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 58150996b8..c61b4abb45 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v47-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v47-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 9e5e1d661e7094ece06bfe6b456375dfef510b24 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v47 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..1f8441acff 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss/nss.h>
+#include <nss/pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v47-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v47-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 56e389f59590285095a3c4c94ce455d406709112 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v47 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 11ca525c6c..f749633ca8 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 766fed67cc..8b8fad7c86 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 13d664dda0..28a6f44250 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..523a77b2c5 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 9467a199c8..2798ca8dd6 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2142,8 +2142,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2fec9143c5..8daf499a35 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index f6a0a23390..9fdc7989ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v47-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v47-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 4d84cd9c2f4c0a72619be3cd73f0108c1443ce14 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v47 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 432 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +--
8 files changed, 664 insertions(+), 212 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index cb24e31c6e..d3d007d1fb 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -16,14 +16,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 81c20aac9e..e505f8552d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d965fbb658..2fec9143c5 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -365,62 +458,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -449,30 +562,33 @@ TODO:
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -484,18 +600,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -510,16 +627,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -527,7 +648,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -552,9 +673,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -562,17 +683,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 59f520cb5b..f6a0a23390 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 83df239e20..68cfd53dc3 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -145,7 +144,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v47-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v47-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 033a188575f3eaaf43f0f916d9989e7889ab6a55 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v47 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1223 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3203 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index fd141a0fa5..a3f45fcef6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9452,7 +9452,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a317aef1c9..add92db87a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..ef8da8b3d4
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..3c7317eac8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4435,7 +4435,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4503,6 +4507,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4531,8 +4545,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..3385429f91
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6c51b2f20f..0d5b15d823 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b288d346f9..79016f912d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..e1574997ee
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
On Fri, Nov 5, 2021 at 6:01 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is a rebase fixing a tiny bug in the documentation which prevented it
from being able to compile.
Hello, I'm looking to help out with reviews for this CF and I'm
currently looking at this patchset.
currently I'm stuck trying to configure:
checking for nss-config... /usr/bin/nss-config
checking for nspr-config... /usr/bin/nspr-config
...
checking nss/ssl.h usability... no
checking nss/ssl.h presence... no
checking for nss/ssl.h... no
configure: error: header file <nss/ssl.h> is required for NSS
This is on fedora 33 and nss-devel is installed, nss-config is
available (and configure finds it) but the directory is different from
Ubuntu:
(base) [vagrant@fedora ~]$ nss-config --includedir
/usr/include/nss3
(base) [vagrant@fedora ~]$ ls -al /usr/include/nss3/ssl.h
-rw-r--r--. 1 root root 70450 Sep 30 05:41 /usr/include/nss3/ssl.h
So if nss-config --includedir is used then #include <ssl.h> should be
used, or if not then #include <nss3/ssl.h> but on this system #include
<nss/ssl.h> is not going to work.
Thanks
On Tue, Nov 9, 2021 at 1:59 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Fri, Nov 5, 2021 at 6:01 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is a rebase fixing a tiny bug in the documentation which prevented it
from being able to compile.Hello, I'm looking to help out with reviews for this CF and I'm
currently looking at this patchset.currently I'm stuck trying to configure:
checking for nss-config... /usr/bin/nss-config
checking for nspr-config... /usr/bin/nspr-config
...
checking nss/ssl.h usability... no
checking nss/ssl.h presence... no
checking for nss/ssl.h... no
configure: error: header file <nss/ssl.h> is required for NSSThis is on fedora 33 and nss-devel is installed, nss-config is
available (and configure finds it) but the directory is different from
Ubuntu:
(base) [vagrant@fedora ~]$ nss-config --includedir
/usr/include/nss3
(base) [vagrant@fedora ~]$ ls -al /usr/include/nss3/ssl.h
-rw-r--r--. 1 root root 70450 Sep 30 05:41 /usr/include/nss3/ssl.hSo if nss-config --includedir is used then #include <ssl.h> should be
used, or if not then #include <nss3/ssl.h> but on this system #include
<nss/ssl.h> is not going to work.
FYI, if I make a symlink to get past this, configure completes but
compilation fails because nspr/nspr.h cannot be found (I'm not sure
why configure doesn't discover this)
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>In file included from protocol_nss.c:24:
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>
^~~~~~~~~~~~~
It's a similar issue:
$ nspr-config --includedir
/usr/include/nspr4
On Tue, Nov 9, 2021 at 2:02 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 9, 2021 at 1:59 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Fri, Nov 5, 2021 at 6:01 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is a rebase fixing a tiny bug in the documentation which prevented it
from being able to compile.Hello, I'm looking to help out with reviews for this CF and I'm
currently looking at this patchset.currently I'm stuck trying to configure:
checking for nss-config... /usr/bin/nss-config
checking for nspr-config... /usr/bin/nspr-config
...
checking nss/ssl.h usability... no
checking nss/ssl.h presence... no
checking for nss/ssl.h... no
configure: error: header file <nss/ssl.h> is required for NSSThis is on fedora 33 and nss-devel is installed, nss-config is
available (and configure finds it) but the directory is different from
Ubuntu:
(base) [vagrant@fedora ~]$ nss-config --includedir
/usr/include/nss3
(base) [vagrant@fedora ~]$ ls -al /usr/include/nss3/ssl.h
-rw-r--r--. 1 root root 70450 Sep 30 05:41 /usr/include/nss3/ssl.hSo if nss-config --includedir is used then #include <ssl.h> should be
used, or if not then #include <nss3/ssl.h> but on this system #include
<nss/ssl.h> is not going to work.FYI, if I make a symlink to get past this, configure completes but
compilation fails because nspr/nspr.h cannot be found (I'm not sure
why configure doesn't discover this)
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>In file included from protocol_nss.c:24:
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>
^~~~~~~~~~~~~It's a similar issue:
$ nspr-config --includedir
/usr/include/nspr4
If these get resolved the next issue is llvm bitcode doesn't compile
because the nss includedir is missing from CPPFLAGS:
/usr/bin/clang -Wno-ignored-attributes -fno-strict-aliasing -fwrapv
-O2 -I../../../src/include -D_GNU_SOURCE -I/usr/include/libxml2
-I/usr/include -flto=thin -emit-llvm -c -o be-secure-nss.bc
be-secure-nss.c
In file included from be-secure-nss.c:20:
In file included from ../../../src/include/common/nss.h:38:
In file included from /usr/include/nss/nss.h:34:
/usr/include/nss/seccomon.h:17:10: fatal error: 'prtypes.h' file not found
#include "prtypes.h"
^~~~~~~~~~~
1 error generated.
On 9 Nov 2021, at 22:22, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 9, 2021 at 2:02 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Tue, Nov 9, 2021 at 1:59 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
Hello, I'm looking to help out with reviews for this CF and I'm
currently looking at this patchset.
Thanks, much appreciated!
currently I'm stuck trying to configure:
checking for nss-config... /usr/bin/nss-config
checking for nspr-config... /usr/bin/nspr-config
...
checking nss/ssl.h usability... no
checking nss/ssl.h presence... no
checking for nss/ssl.h... no
configure: error: header file <nss/ssl.h> is required for NSSThis is on fedora 33 and nss-devel is installed, nss-config is
available (and configure finds it) but the directory is different from
Ubuntu:
(base) [vagrant@fedora ~]$ nss-config --includedir
/usr/include/nss3
(base) [vagrant@fedora ~]$ ls -al /usr/include/nss3/ssl.h
-rw-r--r--. 1 root root 70450 Sep 30 05:41 /usr/include/nss3/ssl.hSo if nss-config --includedir is used then #include <ssl.h> should be
used, or if not then #include <nss3/ssl.h> but on this system #include
<nss/ssl.h> is not going to work.
Interesting rename, I doubt any version but NSS 3 and NSPR 4 is alive anywhere
and an incremented major version seems highly unlikely. Going back to plain
#include <ssl.h> and have the includeflags sort out the correct directories
seems like the best option then. Fixed in the attached.
FYI, if I make a symlink to get past this, configure completes but
compilation fails because nspr/nspr.h cannot be found (I'm not sure
why configure doesn't discover this)
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>In file included from protocol_nss.c:24:
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>
^~~~~~~~~~~~~It's a similar issue:
$ nspr-config --includedir
/usr/include/nspr4
Fixed.
If these get resolved the next issue is llvm bitcode doesn't compile
because the nss includedir is missing from CPPFLAGS:/usr/bin/clang -Wno-ignored-attributes -fno-strict-aliasing -fwrapv
-O2 -I../../../src/include -D_GNU_SOURCE -I/usr/include/libxml2
-I/usr/include -flto=thin -emit-llvm -c -o be-secure-nss.bc
be-secure-nss.c
In file included from be-secure-nss.c:20:
In file included from ../../../src/include/common/nss.h:38:
In file included from /usr/include/nss/nss.h:34:
/usr/include/nss/seccomon.h:17:10: fatal error: 'prtypes.h' file not found
#include "prtypes.h"
^~~~~~~~~~~
1 error generated.
Fixed.
The attached also resolves the conflicts in pgcrypto following db7d1a7b05. PGP
elgamel and RSA pubkey functions aren't supported for now as there is no bignum
functions similar to the BN_* in OpenSSL. I will look into more how hard it
would be to support, for now this gets us ahead.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v48-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v48-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 381bf5f30c7ec084e710981229855b6633d70e42 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v48 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 375 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 532 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index e60e78efdf..4ccb3280a4 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12709,8 +12711,355 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13681,6 +14030,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18480,6 +18846,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18509,7 +18878,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 22cabe1b60..3fa1d994b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1234,7 +1234,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1268,8 +1268,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1459,6 +1529,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2251,6 +2324,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2260,7 +2335,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index ca3592465e..9444ba57ea 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -917,6 +926,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41172eab36..890ef29418 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,7 +462,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index a013951e0d..560a65ea1a 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -496,6 +499,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -557,6 +561,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v48-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v48-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From f0d4593645b9b88ab1403cb311414c7105d27757 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v48 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..85cf138ae5
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v48-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v48-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 8918e8766ebf0d9c69b047a8b6f6da73a81c838f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v48 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v48-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v48-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From f0bbdcd551b37568ae46750382497b856f83801e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v48 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 95 +++
contrib/pgcrypto/expected/cast5_2.out | 48 ++
contrib/pgcrypto/expected/pgp-decrypt_2.out | 421 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 202 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 50 ++
contrib/pgcrypto/nss.c | 601 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 9 +-
12 files changed, 2117 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..26f4485d04
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,95 @@
+--
+-- Blowfish cipher
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- some standard Blowfish testvalues
+SELECT encode(encrypt(
+decode('0000000000000000', 'hex'),
+decode('0000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('ffffffffffffffff', 'hex'),
+decode('ffffffffffffffff', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('1000000000000001', 'hex'),
+decode('3000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('1111111111111111', 'hex'),
+decode('1111111111111111', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('0123456789abcdef', 'hex'),
+decode('fedcba9876543210', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('01a1d6d039776742', 'hex'),
+decode('fedcba9876543210', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('ffffffffffffffff', 'hex'),
+decode('0000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encode(encrypt(
+decode('fedcba9876543210', 'hex'),
+decode('f0e1d2c3b4a5968778695a4b3c2d1e0f', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encode(encrypt(
+decode('01234567890123456789', 'hex'),
+decode('33443344334433443344334433443344', 'hex'),
+'bf-ecb'), 'hex');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encode(encrypt(
+decode('6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5', 'hex'),
+decode('37363534333231204e6f77206973207468652074696d6520666f7220', 'hex'),
+'bf-cbc'), 'hex');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encode(encrypt(
+decode('6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc', 'hex'),
+decode('37363534333231204e6f77206973207468652074696d6520666f722000', 'hex'),
+'bf-cbc'), 'hex');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encode(encrypt(
+decode('fedcba9876543210', 'hex'),
+decode('f0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- result: c04504012e4e1f53
+-- empty data
+select encode(encrypt('', 'foo', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encode(encrypt('foo', '0123456789', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encode(encrypt('foo', '0123456789012345678901', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encode(encrypt_iv('foo', '0123456', 'abcd', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+select decrypt_iv(decode('95c7e89322525d59', 'hex'), '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encode(encrypt('Lets try a longer message.', '0123456789', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+select decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..dd5e92b869
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,48 @@
+--
+-- Cast5 cipher
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12 34 56 78 23 45 67 89 34 56 78 9A', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: 23 8B 4F E5 84 7E 44 B2
+-- 80 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12 34 56 78 23 45', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: EB 6A 71 1A 2C 02 27 1B
+-- 40 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: 7A C8 16 D1 6E 9B 30 2E
+-- cbc
+-- empty data
+select encode( encrypt('', 'foo', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encode( encrypt('foo', '0123456789', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encode(encrypt_iv('foo', '0123456', 'abcd', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select decrypt_iv(decode('384a970695ce016a', 'hex'),
+ '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encode(encrypt('Lets try a longer message.', '0123456789', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..0b5414020c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,421 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+-- expected: 0225e3ede6f2587b076d021a189ff60aad67e066
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ da39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+-- expected: da39a3ee5e6b4b0d3255bfef95601890afd80709
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- expected: 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+-- Checking CRLF
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+-- expected: 9353062be7720f1446d30b9e75573a4833886784
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- expected: 7efefcab38467f7484d6fa43dc86cf5281bd78e2
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- expected: true
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..68709bd15f
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,202 @@
+--
+-- PGP encrypt
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz');
+ pgp_sym_decrypt_bytea
+-----------------------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select encode(pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key'), 'hex');
+ encode
+----------------------
+ 310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select encode(digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1'), 'hex') as result,
+ encode(digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1'), 'hex') as expect;
+ result | expect
+------------------------------------------+------------------------------------------
+ 47bde5d88d6ef8770572b9cbb4278b402aa69966 | 47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..2247c6527c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,50 @@
+--
+-- PGP Public Key Encryption
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..590d0a8559
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,601 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..56b73e033c 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,9 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal> is only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productame>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1159,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1179,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v48-0006-nss-Documentation.patchapplication/octet-stream; name=v48-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 75fc94e94a41daff82adf5003720a6142193ca53 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v48 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3f806740d5..ea964c67cc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..db44c415d0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c17d33a54f..29431ac076 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8714,6 +8887,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8740,6 +8919,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 58150996b8..c61b4abb45 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v48-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v48-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From fb3dffb98af53c1dffdf6c932c7cfa6c4b0a7da5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v48 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..29bcb796c8 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v48-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v48-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From baf121ca3323d6935c9eb422b5def119836432ef Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v48 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 11ca525c6c..f749633ca8 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 766fed67cc..8b8fad7c86 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 13d664dda0..28a6f44250 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..523a77b2c5 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 9467a199c8..2798ca8dd6 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2142,8 +2142,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2fec9143c5..8daf499a35 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index f6a0a23390..9fdc7989ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v48-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v48-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 664584d146641301822f5853a6de7304d7c0df8c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v48 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 432 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +--
8 files changed, 664 insertions(+), 212 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index cb24e31c6e..d3d007d1fb 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -16,14 +16,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 81c20aac9e..e505f8552d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d965fbb658..2fec9143c5 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -365,62 +458,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -449,30 +562,33 @@ TODO:
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -484,18 +600,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -510,16 +627,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -527,7 +648,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -552,9 +673,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -562,17 +683,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 59f520cb5b..f6a0a23390 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 83df239e20..68cfd53dc3 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -145,7 +144,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v48-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v48-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 8f69a473fbd34dea356b9161e44930c52852974b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v48 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2f30d11763..d965fbb658 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,32 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -76,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -613,7 +575,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 983554263f..59f520cb5b 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c5999e0b33..83df239e20 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v48-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v48-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 623e6304d3196c8c1ca5480fac1a835825e58f44 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v48 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1223 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3203 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index fd141a0fa5..a3f45fcef6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9452,7 +9452,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 7bcf52523b..9d0e30f248 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..dfa4581caa
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secoidt.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..3c7317eac8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4435,7 +4435,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4503,6 +4507,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4531,8 +4545,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..10455ecbe5
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6b67a2a318..351520901e 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 614035e215..6af89d9e98 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -185,7 +185,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b6a6939f0..3aa68a336b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..8b1f9f7067
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1223 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+
+#include <hasht.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
On Wed, Nov 10, 2021 at 8:49 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 9 Nov 2021, at 22:22, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 9, 2021 at 2:02 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Tue, Nov 9, 2021 at 1:59 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:Hello, I'm looking to help out with reviews for this CF and I'm
currently looking at this patchset.Thanks, much appreciated!
currently I'm stuck trying to configure:
checking for nss-config... /usr/bin/nss-config
checking for nspr-config... /usr/bin/nspr-config
...
checking nss/ssl.h usability... no
checking nss/ssl.h presence... no
checking for nss/ssl.h... no
configure: error: header file <nss/ssl.h> is required for NSSThis is on fedora 33 and nss-devel is installed, nss-config is
available (and configure finds it) but the directory is different from
Ubuntu:
(base) [vagrant@fedora ~]$ nss-config --includedir
/usr/include/nss3
(base) [vagrant@fedora ~]$ ls -al /usr/include/nss3/ssl.h
-rw-r--r--. 1 root root 70450 Sep 30 05:41 /usr/include/nss3/ssl.hSo if nss-config --includedir is used then #include <ssl.h> should be
used, or if not then #include <nss3/ssl.h> but on this system #include
<nss/ssl.h> is not going to work.Interesting rename, I doubt any version but NSS 3 and NSPR 4 is alive anywhere
and an incremented major version seems highly unlikely. Going back to plain
#include <ssl.h> and have the includeflags sort out the correct directories
seems like the best option then. Fixed in the attached.FYI, if I make a symlink to get past this, configure completes but
compilation fails because nspr/nspr.h cannot be found (I'm not sure
why configure doesn't discover this)
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>In file included from protocol_nss.c:24:
../../src/include/common/nss.h:31:10: fatal error: 'nspr/nspr.h' file not found
#include <nspr/nspr.h>
^~~~~~~~~~~~~It's a similar issue:
$ nspr-config --includedir
/usr/include/nspr4Fixed.
If these get resolved the next issue is llvm bitcode doesn't compile
because the nss includedir is missing from CPPFLAGS:/usr/bin/clang -Wno-ignored-attributes -fno-strict-aliasing -fwrapv
-O2 -I../../../src/include -D_GNU_SOURCE -I/usr/include/libxml2
-I/usr/include -flto=thin -emit-llvm -c -o be-secure-nss.bc
be-secure-nss.c
In file included from be-secure-nss.c:20:
In file included from ../../../src/include/common/nss.h:38:
In file included from /usr/include/nss/nss.h:34:
/usr/include/nss/seccomon.h:17:10: fatal error: 'prtypes.h' file not found
#include "prtypes.h"
^~~~~~~~~~~
1 error generated.Fixed.
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
/usr/bin/clang -Wno-ignored-attributes -fno-strict-aliasing -fwrapv
-O2 -I../../../src/include -D_GNU_SOURCE -I/usr/include/libxml2
-I/usr/include -flto=thin -emit-llvm -c -o be-secure-nss.bc
be-secure-nss.c
In file included from be-secure-nss.c:20:
../../../src/include/common/nss.h:31:10: fatal error: 'nspr.h' file not found
#include <nspr.h>
^~~~~~~~
1 error generated.
FWIW I attached the Dockerfile I've been using to test this, primarily
to ensure that there were no openssl devel files lurking around during
compilation.
It expects a ./postgres directory with whatever patches already applied to it.
Show quoted text
The attached also resolves the conflicts in pgcrypto following db7d1a7b05. PGP
elgamel and RSA pubkey functions aren't supported for now as there is no bignum
functions similar to the BN_* in OpenSSL. I will look into more how hard it
would be to support, for now this gets us ahead.--
Daniel Gustafsson https://vmware.com/
Attachments:
On 15 Nov 2021, at 20:51, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
Interesting, I am unable to replicate that in my tree but I'll investigate
further tomorrow using your Dockerfile. For the sake of testing, does
compilation pass for you in the same place without using --with-llvm?
--
Daniel Gustafsson https://vmware.com/
On Mon, Nov 15, 2021 at 4:44 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 Nov 2021, at 20:51, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
Interesting, I am unable to replicate that in my tree but I'll investigate
further tomorrow using your Dockerfile. For the sake of testing, does
compilation pass for you in the same place without using --with-llvm?
Yes, it builds and check-world passes. I'll continue testing with this
build. Thank you.
On Mon, Nov 15, 2021 at 5:37 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Mon, Nov 15, 2021 at 4:44 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 Nov 2021, at 20:51, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
Interesting, I am unable to replicate that in my tree but I'll investigate
further tomorrow using your Dockerfile. For the sake of testing, does
compilation pass for you in the same place without using --with-llvm?Yes, it builds and check-world passes. I'll continue testing with this
build. Thank you.
The previous Dockerfile had some issues due to a hasty port from RHEL
to Fedora, attached is one that works with your patchset, llvm
currently disabled, and the llvm deps removed.
The service file is also attached since it's referenced in the
Dockerfile and you'd have had to reproduce it.
After building, run with:
docker run --name pg-test -p 5432:5432 --cap-add=SYS_ADMIN -v
/sys/fs/cgroup:/sys/fs/cgroup:ro -d <final docker hash>
On Tue, Nov 16, 2021 at 9:45 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Mon, Nov 15, 2021 at 5:37 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Mon, Nov 15, 2021 at 4:44 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 Nov 2021, at 20:51, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
Interesting, I am unable to replicate that in my tree but I'll investigate
further tomorrow using your Dockerfile. For the sake of testing, does
compilation pass for you in the same place without using --with-llvm?Yes, it builds and check-world passes. I'll continue testing with this
build. Thank you.The previous Dockerfile had some issues due to a hasty port from RHEL
to Fedora, attached is one that works with your patchset, llvm
currently disabled, and the llvm deps removed.The service file is also attached since it's referenced in the
Dockerfile and you'd have had to reproduce it.After building, run with:
docker run --name pg-test -p 5432:5432 --cap-add=SYS_ADMIN -v
/sys/fs/cgroup:/sys/fs/cgroup:ro -d <final docker hash>
I think there it a typo in the docs here that prevents them from
building (this diff seems to fix it):
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 56b73e033c..844aa31e86 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -767,7 +767,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1,
cipher-algo=aes256')
<para>
Which cipher algorithm to use. <literal>cast5</literal> is only available
if <productname>PostgreSQL</productname> was built with
- <productname>OpenSSL</productame>.
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
On Tue, Nov 16, 2021 at 1:26 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 16, 2021 at 9:45 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Mon, Nov 15, 2021 at 5:37 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Mon, Nov 15, 2021 at 4:44 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 15 Nov 2021, at 20:51, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
Apologies for the delay, this didn't go to my inbox and I missed it on list.
The bitcode generation is still broken, this time for nspr.h:
Interesting, I am unable to replicate that in my tree but I'll investigate
further tomorrow using your Dockerfile. For the sake of testing, does
compilation pass for you in the same place without using --with-llvm?Yes, it builds and check-world passes. I'll continue testing with this
build. Thank you.The previous Dockerfile had some issues due to a hasty port from RHEL
to Fedora, attached is one that works with your patchset, llvm
currently disabled, and the llvm deps removed.The service file is also attached since it's referenced in the
Dockerfile and you'd have had to reproduce it.After building, run with:
docker run --name pg-test -p 5432:5432 --cap-add=SYS_ADMIN -v
/sys/fs/cgroup:/sys/fs/cgroup:ro -d <final docker hash>I think there it a typo in the docs here that prevents them from
building (this diff seems to fix it):diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml index 56b73e033c..844aa31e86 100644 --- a/doc/src/sgml/pgcrypto.sgml +++ b/doc/src/sgml/pgcrypto.sgml @@ -767,7 +767,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256') <para> Which cipher algorithm to use. <literal>cast5</literal> is only available if <productname>PostgreSQL</productname> was built with - <productname>OpenSSL</productame>. + <productname>OpenSSL</productname>. </para> <literallayout> Values: bf, aes128, aes192, aes256, 3des, cast5
After a bit more testing, the server is up and running with an nss
database but before configuring the client database I tried connecting
and got a segfault:
#0 PR_Write (fd=0x0, buf=0x141ba60, amount=84) at
io/../../.././nspr/pr/src/io/priometh.c:114
#1 0x00007ff33dfdc62f in pgtls_write (conn=0x13cecb0, ptr=0x141ba60,
len=84) at fe-secure-nss.c:583
#2 0x00007ff33dfd6e18 in pqsecure_write (conn=0x13cecb0,
ptr=0x141ba60, len=84) at fe-secure.c:295
#3 0x00007ff33dfd04dc in pqSendSome (conn=0x13cecb0, len=84) at fe-misc.c:834
#4 0x00007ff33dfd06c8 in pqFlush (conn=0x13cecb0) at fe-misc.c:972
#5 0x00007ff33dfc257c in pqPacketSend (conn=0x13cecb0, pack_type=0
'\000', buf=0x1414c60, buf_len=80) at fe-connect.c:4619
#6 0x00007ff33dfbfadd in PQconnectPoll (conn=0x13cecb0) at fe-connect.c:2986
#7 0x00007ff33dfbe55c in connectDBComplete (conn=0x13cecb0) at
fe-connect.c:2218
#8 0x00007ff33dfbbaef in PQconnectdbParams (keywords=0x1427d10,
values=0x1427e60, expand_dbname=1) at fe-connect.c:668
#9 0x000000000043ebc7 in main (argc=2, argv=0x7ffdccd0e2f8) at startup.c:273
It looks like the ssl connection falls through to attempt a non-ssl
connection but at some point conn->ssl_in_use gets set to true,
despite pr_fd and nss_context being null.
This patch fixes the segfault but I suspect is not the correct fix,
due to the error when connecting saying "Success":
--- a/src/interfaces/libpq/fe-secure-nss.c
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -498,6 +498,11 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
* for closed connections, while -1 indicates an error within
the ongoing
* connection.
*/
+ if (!conn->pr_fd) {
+ SOCK_ERRNO_SET(read_errno);
+ return -1;
+ }
+
nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
if (nread == 0)
@@ -580,6 +585,11 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
PRErrorCode status;
int write_errno = 0;
+ if (!conn->pr_fd) {
+ SOCK_ERRNO_SET(write_errno);
+ return -1;
+ }
+
n = PR_Write(conn->pr_fd, ptr, len);
if (n < 0)
On 17 Nov 2021, at 19:42, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 16, 2021 at 1:26 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
I think there it a typo in the docs here that prevents them from
building (this diff seems to fix it):
Ah yes, thanks, I had noticed that one but forgot to send out a new version to
make the CFBot green.
After a bit more testing, the server is up and running with an nss
database but before configuring the client database I tried connecting
and got a segfault:
Interesting. I'm unable to reproduce this crash, can you show the sequence of
commands which led to this?
It looks like the ssl connection falls through to attempt a non-ssl
connection but at some point conn->ssl_in_use gets set to true,
despite pr_fd and nss_context being null.
pgtls_close missed setting ssl_in_use to false, fixed in the attached. I've
also added some assertions to the connection setup for debugging this.
This patch fixes the segfault but I suspect is not the correct fix,
due to the error when connecting saying "Success":
Right, without an SSL enabled FD we should never get here.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v49-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v49-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 7759149588610dfe194098db6159c34296a11b6e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v49 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1574 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1229 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3209 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 786781db4b..18a7b188f1 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9505,7 +9505,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 7bcf52523b..9d0e30f248 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..dfa4581caa
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1574 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secoidt.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfd..3c7317eac8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4435,7 +4435,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4503,6 +4507,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4531,8 +4545,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..10455ecbe5
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6b67a2a318..351520901e 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 225c5b87cc..c431645ba0 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -202,7 +202,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b6a6939f0..3aa68a336b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..cc5bc1d80d
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1229 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+
+#include <hasht.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶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 = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ Assert(conn->nss_context);
+ Assert(conn->pr_fd);
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ status = SECFailure;
+ goto done;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
v49-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v49-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From c2bc097628f204f6ca89b265ac75dcf00cdd4a9a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v49 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 56 ++--------
src/test/ssl/t/002_scram.pl | 6 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 83 +++++++++++---
4 files changed, 181 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2f30d11763..d965fbb658 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,32 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
- "client", "client-revoked",
- "client-der", "client-encrypted-pem",
- "client-encrypted-der", "client-dn");
-foreach my $key (@keys)
-{
- copy("ssl/${key}.key", "ssl/${key}_tmp.key")
- or die
- "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
- chmod 0600, "ssl/${key}_tmp.key"
- or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -76,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -613,7 +575,5 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 983554263f..59f520cb5b 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
@@ -115,5 +115,3 @@ $node->connect_ok(
# clean up
unlink($client_tmp_key);
-
-done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..e77ee25cc7
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ my @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der", "client-dn");
+ foreach my $key (@keys)
+ {
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die
+ "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
+ chmod 0644, "ssl/client_wrongperms_tmp.key";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c5999e0b33..83df239e20 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,19 +26,39 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
);
@@ -112,14 +132,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -127,20 +154,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -150,13 +193,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v49-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v49-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 5ee978e770ec1154be5a996047004a121aedb948 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v49 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 ++++++++++++++++
src/test/ssl/t/001_ssltests.pl | 432 +++++++++++++++++---------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 61 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 19 +-
src/test/ssl/t/SSL/Server.pm | 48 +--
8 files changed, 664 insertions(+), 212 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index cb24e31c6e..d3d007d1fb 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -16,14 +16,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 81c20aac9e..e505f8552d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index d965fbb658..2fec9143c5 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -365,62 +458,82 @@ $node->connect_ok(
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -449,30 +562,33 @@ TODO:
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -484,18 +600,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
@@ -510,16 +627,20 @@ $node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -527,7 +648,7 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
@@ -552,9 +673,9 @@ $node->connect_ok(
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -562,17 +683,18 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
# clean up
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 59f520cb5b..f6a0a23390 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,26 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+ plan tests => $supports_tls_server_end_point ? 11 : 12;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+ plan tests => 11;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +45,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +63,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -99,7 +112,7 @@ my $client_tmp_key = "ssl/client_scram_tmp.key";
copy("ssl/client.key", $client_tmp_key);
chmod 0600, $client_tmp_key;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -107,7 +120,7 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..20633fe41b
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,61 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index e77ee25cc7..9e419b0e0f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -71,16 +71,19 @@ sub init
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 83df239e20..68cfd53dc3 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -58,7 +58,6 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
);
@@ -145,7 +144,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -170,46 +169,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v49-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v49-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 04291d97919c4bbaea93fbd062817f3d540750ac Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v49 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
src/test/ssl/t/002_scram.pl | 4 ++--
7 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 11ca525c6c..f749633ca8 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 766fed67cc..8b8fad7c86 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 13d664dda0..28a6f44250 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..523a77b2c5 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 9467a199c8..2798ca8dd6 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2142,8 +2142,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2fec9143c5..8daf499a35 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index f6a0a23390..9fdc7989ab 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -27,13 +27,13 @@ if ($ENV{with_ssl} eq 'openssl')
# Determine whether build supports tls-server-end-point.
$supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
- plan tests => $supports_tls_server_end_point ? 11 : 12;
+ plan tests => 15;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
$supports_tls_server_end_point = 1;
- plan tests => 11;
+ plan tests => 15;
}
else
{
--
2.24.3 (Apple Git-128)
v49-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v49-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 1f099e22e60251a6357bda011609f5bf24863723 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v49 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..29bcb796c8 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v49-0006-nss-Documentation.patchapplication/octet-stream; name=v49-0006-nss-Documentation.patch; x-unix-mode=0644Download
From faa35b6ff302febbabf01ad8b10196cd26a0b961 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v49 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3f806740d5..ea964c67cc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..db44c415d0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c17d33a54f..29431ac076 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8714,6 +8887,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8740,6 +8919,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 58150996b8..c61b4abb45 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2375,7 +2398,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2422,6 +2445,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2445,7 +2476,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2549,6 +2580,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v49-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v49-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 88baafadb8af8950ff2364fd4a520c8a658f6d8d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v49 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 95 +++
contrib/pgcrypto/expected/cast5_2.out | 48 ++
contrib/pgcrypto/expected/pgp-decrypt_2.out | 421 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 202 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 50 ++
contrib/pgcrypto/nss.c | 601 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 9 +-
12 files changed, 2117 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..26f4485d04
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,95 @@
+--
+-- Blowfish cipher
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- some standard Blowfish testvalues
+SELECT encode(encrypt(
+decode('0000000000000000', 'hex'),
+decode('0000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('ffffffffffffffff', 'hex'),
+decode('ffffffffffffffff', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('1000000000000001', 'hex'),
+decode('3000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('1111111111111111', 'hex'),
+decode('1111111111111111', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('0123456789abcdef', 'hex'),
+decode('fedcba9876543210', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('01a1d6d039776742', 'hex'),
+decode('fedcba9876543210', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encode(encrypt(
+decode('ffffffffffffffff', 'hex'),
+decode('0000000000000000', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encode(encrypt(
+decode('fedcba9876543210', 'hex'),
+decode('f0e1d2c3b4a5968778695a4b3c2d1e0f', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encode(encrypt(
+decode('01234567890123456789', 'hex'),
+decode('33443344334433443344334433443344', 'hex'),
+'bf-ecb'), 'hex');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encode(encrypt(
+decode('6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5', 'hex'),
+decode('37363534333231204e6f77206973207468652074696d6520666f7220', 'hex'),
+'bf-cbc'), 'hex');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encode(encrypt(
+decode('6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc', 'hex'),
+decode('37363534333231204e6f77206973207468652074696d6520666f722000', 'hex'),
+'bf-cbc'), 'hex');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encode(encrypt(
+decode('fedcba9876543210', 'hex'),
+decode('f0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff', 'hex'),
+'bf-ecb/pad:none'), 'hex');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- result: c04504012e4e1f53
+-- empty data
+select encode(encrypt('', 'foo', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encode(encrypt('foo', '0123456789', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encode(encrypt('foo', '0123456789012345678901', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encode(encrypt_iv('foo', '0123456', 'abcd', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+select decrypt_iv(decode('95c7e89322525d59', 'hex'), '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encode(encrypt('Lets try a longer message.', '0123456789', 'bf'), 'hex');
+ERROR: Cannot use "bf": No such cipher algorithm
+select decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..dd5e92b869
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,48 @@
+--
+-- Cast5 cipher
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12 34 56 78 23 45 67 89 34 56 78 9A', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: 23 8B 4F E5 84 7E 44 B2
+-- 80 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12 34 56 78 23 45', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: EB 6A 71 1A 2C 02 27 1B
+-- 40 bit key
+SELECT encode(encrypt(
+decode('01 23 45 67 89 AB CD EF', 'hex'),
+decode('01 23 45 67 12', 'hex'),
+'cast5-ecb/pad:none'), 'hex');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- result: 7A C8 16 D1 6E 9B 30 2E
+-- cbc
+-- empty data
+select encode( encrypt('', 'foo', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encode( encrypt('foo', '0123456789', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encode(encrypt_iv('foo', '0123456', 'abcd', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select decrypt_iv(decode('384a970695ce016a', 'hex'),
+ '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encode(encrypt('Lets try a longer message.', '0123456789', 'cast5'), 'hex');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..0b5414020c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,421 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+-- expected: 0225e3ede6f2587b076d021a189ff60aad67e066
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ da39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+-- expected: da39a3ee5e6b4b0d3255bfef95601890afd80709
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- expected: 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+-- Checking CRLF
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+-- expected: 9353062be7720f1446d30b9e75573a4833886784
+select encode(digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1'), 'hex');
+ encode
+------------------------------------------
+ 7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- expected: 7efefcab38467f7484d6fa43dc86cf5281bd78e2
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- expected: true
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..68709bd15f
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,202 @@
+--
+-- PGP encrypt
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz');
+ pgp_sym_decrypt_bytea
+-----------------------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select encode(pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key'), 'hex');
+ encode
+----------------------
+ 310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select encode(digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1'), 'hex') as result,
+ encode(digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1'), 'hex') as expect;
+ result | expect
+------------------------------------------+------------------------------------------
+ 47bde5d88d6ef8770572b9cbb4278b402aa69966 | 47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..2247c6527c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,50 @@
+--
+-- PGP Public Key Encryption
+--
+-- ensure consistent test output regardless of the default bytea format
+SET bytea_output TO escape;
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..590d0a8559
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,601 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..844aa31e86 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,9 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal> is only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1159,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1179,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v49-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v49-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 5c03623985cc4fc2b866e27d25cf0b26381121aa Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v49 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++++++++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.24.3 (Apple Git-128)
v49-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v49-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 80de919fd7ef484d5e7cdfaa0c638c29ba000dbf Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v49 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..85cf138ae5
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v49-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v49-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 26e1da8f9b26dd544d9709c8dc24a0df1604f06e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v49 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 375 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 532 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 896b781473..80592cca46 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12931,8 +12933,355 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13903,6 +14252,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18702,6 +19068,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18731,7 +19100,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index b50130b323..d95bc29bed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1270,7 +1270,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1304,8 +1304,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1495,6 +1565,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2287,6 +2360,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
else
@@ -2296,7 +2371,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 880722fcf5..8a75ca0df3 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,6 +85,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -92,6 +99,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index ca3592465e..9444ba57ea 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -917,6 +926,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 41172eab36..890ef29418 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -137,6 +137,12 @@ sub mkvcbuild
push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
+ elsif ($solution->{options}->{nss})
+ {
+ push(@pgcommonallfiles, 'cryptohash_nss.c');
+ push(@pgcommonallfiles, 'cipher_nss.c');
+ push(@pgcommonallfiles, 'protocol_nss.c');
+ }
else
{
push(@pgcommonallfiles, 'cryptohash.c');
@@ -202,12 +208,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -265,12 +278,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -442,7 +462,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index a013951e0d..560a65ea1a 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -496,6 +499,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -557,6 +561,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
On Tue, Nov 23, 2021 at 9:12 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 17 Nov 2021, at 19:42, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Tue, Nov 16, 2021 at 1:26 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:I think there it a typo in the docs here that prevents them from
building (this diff seems to fix it):Ah yes, thanks, I had noticed that one but forgot to send out a new version to
make the CFBot green.After a bit more testing, the server is up and running with an nss
database but before configuring the client database I tried connecting
and got a segfault:Interesting. I'm unable to reproduce this crash, can you show the sequence of
commands which led to this?
It no longer happens with v49, since it was a null deref of the pr_fd
which no longer happens.
I'll continue testing now, so far it's looking better.
Did the build issue with --with-llvm get fixed in this update also? I
haven't tried building with it yet.
It looks like the ssl connection falls through to attempt a non-ssl
connection but at some point conn->ssl_in_use gets set to true,
despite pr_fd and nss_context being null.pgtls_close missed setting ssl_in_use to false, fixed in the attached. I've
also added some assertions to the connection setup for debugging this.This patch fixes the segfault but I suspect is not the correct fix,
due to the error when connecting saying "Success":Right, without an SSL enabled FD we should never get here.
Thank you.
On 23 Nov 2021, at 23:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
It no longer happens with v49, since it was a null deref of the pr_fd
which no longer happens.I'll continue testing now, so far it's looking better.
Great, thanks for confirming. I'm still keen on knowing how you triggered the
segfault so I can ensure there are no further bugs around there.
--
Daniel Gustafsson https://vmware.com/
On Wed, Nov 24, 2021 at 6:59 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Nov 2021, at 23:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
It no longer happens with v49, since it was a null deref of the pr_fd
which no longer happens.I'll continue testing now, so far it's looking better.
Great, thanks for confirming. I'm still keen on knowing how you triggered the
segfault so I can ensure there are no further bugs around there.
It happened when I ran psql with hostssl on the server but before I'd
initialized my client certificate store.
On Wed, Nov 24, 2021 at 8:46 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Wed, Nov 24, 2021 at 6:59 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Nov 2021, at 23:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
It no longer happens with v49, since it was a null deref of the pr_fd
which no longer happens.I'll continue testing now, so far it's looking better.
Great, thanks for confirming. I'm still keen on knowing how you triggered the
segfault so I can ensure there are no further bugs around there.It happened when I ran psql with hostssl on the server but before I'd
initialized my client certificate store.
I don't know enough about NSS to know if this is problematic or not
but if I try verify-full without having the root CA in the certificate
store I get:
$ /usr/pgsql-15/bin/psql "host=localhost sslmode=verify-full user=postgres"
psql: error: SSL error: Issuer certificate is invalid.
unable to shut down NSS context: NSS could not shutdown. Objects are
still in use.
On Wed, Nov 24, 2021 at 8:49 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Wed, Nov 24, 2021 at 8:46 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Wed, Nov 24, 2021 at 6:59 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Nov 2021, at 23:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
It no longer happens with v49, since it was a null deref of the pr_fd
which no longer happens.I'll continue testing now, so far it's looking better.
Great, thanks for confirming. I'm still keen on knowing how you triggered the
segfault so I can ensure there are no further bugs around there.It happened when I ran psql with hostssl on the server but before I'd
initialized my client certificate store.I don't know enough about NSS to know if this is problematic or not
but if I try verify-full without having the root CA in the certificate
store I get:$ /usr/pgsql-15/bin/psql "host=localhost sslmode=verify-full user=postgres"
psql: error: SSL error: Issuer certificate is invalid.
unable to shut down NSS context: NSS could not shutdown. Objects are
still in use.
Something is strange with ssl downgrading and a bad ssldatabase
[postgres@11cdfa30f763 ~]$ /usr/pgsql-15/bin/psql "ssldatabase=oops
sslcert=client_cert host=localhost"
Password for user postgres:
<freezes here>
On the server side:
2021-11-25 01:52:01.984 UTC [269] LOG: unable to handshake:
Encountered end of file (PR_END_OF_FILE_ERROR)
Other than that and I still haven't tested --with-llvm I've gotten
everything working, including with an openssl client. Attached is a
dockerfile that gets to the point where a client can connect with
clientcert=verify-full. I've removed some of the old cruft and
debugging from the previous versions.
Thank you.
Attachments:
On Mon, 2021-09-27 at 15:44 +0200, Daniel Gustafsson wrote:
Speaking of IP addresses in SANs, it doesn't look like our OpenSSL
backend can handle those. That's a separate conversation, but I might
take a look at a patch for next commitfest.Please do.
Didn't get around to it for November, but I'm putting the finishing
touches on that now.
While I was looking at the new SAN code (in fe-secure-nss.c,
pgtls_verify_peer_name_matches_certificate_guts()), I noticed that code
coverage never seemed to touch a good chunk of it:
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn)) + { + char *alt_name; + int rv; + char tmp[512];
That loop can never execute. But I wonder if all of that extra SAN code
should be removed anyway? There's this comment above it:
+ /* + * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName + * verification. + */
and it seems like SAN verification is working in my testing, despite
the dead loop.
--Jacob
On 25 Nov 2021, at 14:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Wed, Nov 24, 2021 at 8:49 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Wed, Nov 24, 2021 at 8:46 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
I don't know enough about NSS to know if this is problematic or not
but if I try verify-full without having the root CA in the certificate
store I get:$ /usr/pgsql-15/bin/psql "host=localhost sslmode=verify-full user=postgres"
psql: error: SSL error: Issuer certificate is invalid.
unable to shut down NSS context: NSS could not shutdown. Objects are
still in use.
Fixed.
Something is strange with ssl downgrading and a bad ssldatabase
[postgres@11cdfa30f763 ~]$ /usr/pgsql-15/bin/psql "ssldatabase=oops
sslcert=client_cert host=localhost"
Password for user postgres:<freezes here>
Also fixed.
On the server side:
2021-11-25 01:52:01.984 UTC [269] LOG: unable to handshake:
Encountered end of file (PR_END_OF_FILE_ERROR)
This is normal and expected, but to make it easier on users I've changed this
error message to be aligned with the OpenSSL implementation.
Other than that and I still haven't tested --with-llvm I've gotten
everything working, including with an openssl client. Attached is a
dockerfile that gets to the point where a client can connect with
clientcert=verify-full. I've removed some of the old cruft and
debugging from the previous versions.
Very cool, thanks! I've been unable to reproduce any issues with llvm but I'll
keep poking at that. A new version will be posted shortly with the above and a
few more fixes.
--
Daniel Gustafsson https://vmware.com/
On 30 Nov 2021, at 20:03, Jacob Champion <pchampion@vmware.com> wrote:
On Mon, 2021-09-27 at 15:44 +0200, Daniel Gustafsson wrote:
Speaking of IP addresses in SANs, it doesn't look like our OpenSSL
backend can handle those. That's a separate conversation, but I might
take a look at a patch for next commitfest.Please do.
Didn't get around to it for November, but I'm putting the finishing
touches on that now.
Cool, thanks!
While I was looking at the new SAN code (in fe-secure-nss.c,
pgtls_verify_peer_name_matches_certificate_guts()), I noticed that code
coverage never seemed to touch a good chunk of it:+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn)) + { + char *alt_name; + int rv; + char tmp[512];That loop can never execute. But I wonder if all of that extra SAN code
should be removed anyway? There's this comment above it:+ /* + * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName + * verification. + */and it seems like SAN verification is working in my testing, despite
the dead loop.
Yeah, that's clearly bogus. I followed the bouncing ball reading NSS code and
from what I can tell the comment is correct. I removed the dead code, only
realizing after the fact that I might cause conflict with your tree doing so,
in that case sorry.
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v50-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v50-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 00d4b7c6790b44e3974bf93ae616144335b715cc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v50 10/10] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 375 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 532 insertions(+), 12 deletions(-)
diff --git a/configure b/configure
index 3b19105328..25388a75a2 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12931,8 +12933,355 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13903,6 +14252,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18670,6 +19036,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18702,7 +19071,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index e77d4dcf2d..e2307a7aa2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1270,7 +1270,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1304,8 +1304,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1495,6 +1565,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2292,6 +2365,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
elif test x"$cross_compiling" = x"yes"; then
@@ -2303,7 +2378,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 31c0dd366d..501009ce2a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 7525c16597..a89e921767 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -914,6 +923,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 40a96c881d..dc56228ec8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index c932322e35..d55611b024 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,7 +440,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 404c45a6f3..4734f2c894 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,6 +138,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');
@@ -203,12 +209,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -266,12 +279,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -443,7 +463,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 2c8cd521e9..205a47b408 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -495,6 +498,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -556,6 +560,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1015,6 +1026,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v50-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v50-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 628fd9da361d946be711fc80911928eb3f4cb2d1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v50 09/10] nss: Support NSS in cryptohash
This adds support for using libnss as a crypto backend for the built-
in cryptohash support. Much of this code is similar to the pgcrypto
support.
---
src/common/cryptohash_nss.c | 264 ++++++++++++++++++++++++++++++++++++
1 file changed, 264 insertions(+)
create mode 100644 src/common/cryptohash_nss.c
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..85cf138ae5
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,264 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+ ctx->type = type;
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ return -1;
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
--
2.24.3 (Apple Git-128)
v50-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v50-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 0268781a38f5518e7ab71d95203b7e98ccb4aefd Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v50 08/10] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++-
src/test/ssl/sslfiles_nss.mk | 12 +++++-
src/test/ssl/t/003_sslinfo.pl | 80 +++++++++++++++++------------------
4 files changed, 93 insertions(+), 43 deletions(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
index adb7363d63..3b4e2494a2 100644
--- a/src/test/ssl/sslfiles_nss.mk
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -43,7 +43,8 @@ NSSFILES := ssl/nss/client_ca.crt.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
+ ssl/nss/native_client-root.db \
+ ssl/nss/client_ext.crt__client_ext.key.db
nssfiles: $(NSSFILES)
@@ -168,6 +169,15 @@ ssl/nss/client.crt__client.key.db: ssl/client.crt
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 ''
+ssl/nss/client_ext.crt__client_ext.key.db: ssl/client_ext.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client_ext.crt -i ssl/client_ext.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client_ext.pfx -inkey ssl/client_ext.key -in ssl/client_ext.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client_ext.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) $@
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index f3c6db2989..9181a74437 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,18 +14,8 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} eq 'openssl')
-{
- plan tests => 13;
-}
-elsif ($ENV{with_ssl} eq 'nss')
-{
- plan skip_all => 'SSL not supported by this build';
-}
-else
-{
- plan skip_all => 'SSL not supported by this build';
-}
+plan skip_all => 'SSL not supported by this build'
+ unless (($ENV{with_ssl} eq 'openssl') || ($ENV{with_ssl} eq 'nss'));
#### Some configuration
@@ -68,10 +58,11 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
# We aren't using any CRL's in this suite so we can keep using server-revoked
# as server certificate for simple client.crt connection much like how the
# 001 test does.
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR " .
+ "ssldatabase=ssl/nss/client_ext.crt__client_ext.key.db " .
"user=ssltestuser sslcert=ssl/client_ext.crt sslkey=$client_tmp_key";
# Make sure we can connect even though previous test suites have established this
@@ -110,32 +101,39 @@ $result = $node->safe_psql("certdb",
connstr => $common_connstr);
is($result, 't', "ssl_client_serial() compared with pg_stat_ssl");
-# Must not use safe_psql since we expect an error here
-$result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
- connstr => $common_connstr);
-is($result, '3', "ssl_client_dn_field() for an invalid field");
-
-$result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
- connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
- "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
-is($result, '', "ssl_client_dn_field() for connection without cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_client_dn_field() for commonName");
-
-$result = $node->safe_psql("certdb",
- "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_dn() for connection with cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_field() for commonName");
+SKIP:
+{
+ skip "Functionality not implemented with NSS", 6 if ($ENV{with_ssl} eq 'nss');
+
+ # Must not use safe_psql since we expect an error here
+ $result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
+ connstr => $common_connstr);
+ is($result, '3', "ssl_client_dn_field() for an invalid field");
+
+ $result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
+ connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
+ "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
+ is($result, '', "ssl_client_dn_field() for connection without cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_client_dn_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_dn() for connection with cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
+ connstr => $common_connstr);
+ is($result, 'CA:FALSE|t', 'extract extension from cert');
+}
-$result = $node->safe_psql("certdb",
- "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
- connstr => $common_connstr);
-is($result, 'CA:FALSE|t', 'extract extension from cert');
+done_testing();
--
2.24.3 (Apple Git-128)
v50-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v50-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 98a3b21c4f3555b82d143da18f02d79b02e71ca5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v50 07/10] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 62 ++
contrib/pgcrypto/expected/cast5_2.out | 33 +
contrib/pgcrypto/expected/pgp-decrypt_2.out | 415 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 200 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 48 ++
contrib/pgcrypto/nss.c | 601 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 10 +-
12 files changed, 2060 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..5b8b55b84f
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,62 @@
+--
+-- Blowfish cipher
+--
+-- some standard Blowfish testvalues
+SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5',
+ '\x37363534333231204e6f77206973207468652074696d6520666f7220',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc',
+ '\x37363534333231204e6f77206973207468652074696d6520666f722000',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encrypt('\xfedcba9876543210',
+ '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff',
+ 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- empty data
+select encrypt('', 'foo', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..5e829609b0
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,33 @@
+--
+-- Cast5 cipher
+--
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 80 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 40 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- cbc
+-- empty data
+select encrypt('', 'foo', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..6ff774b190
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,415 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \xda39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- Checking CRLF
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1');
+ digest
+--------------------------------------------
+ \x9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1');
+ digest
+--------------------------------------------
+ \x7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..168dc9bafa
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,200 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key');
+ pgp_sym_decrypt_bytea
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1') as result,
+ digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+ result | expect
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..a5dd6d092c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,48 @@
+--
+-- PGP Public Key Encryption
+--
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select encode(pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey)), 'escape')
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..590d0a8559
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,601 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..2e3fc914e1 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,10 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal>, and
+ <literal>blowfish</literal> are only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1160,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1180,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v50-0006-nss-Documentation.patchapplication/octet-stream; name=v50-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 973e878496a0bd589f334417c990b5e1a22baef2 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v50 06/10] nss: Documentation
Basic documentation of the new API (keypass hooks) as well as config
parameters and installation. Additionally there is a section on how
to create certificate and keys using the NSS toolchain.
---
doc/src/sgml/acronyms.sgml | 33 ++++
doc/src/sgml/config.sgml | 30 ++-
doc/src/sgml/installation.sgml | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index afbb6c35e3..4712910b50 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..db44c415d0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 14f35d37f6..67dd923fce 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL server certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8716,6 +8889,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8742,6 +8921,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f77ed24204..132e8cbf80 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v50-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v50-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From e56e02d791849461ba48dd1bd756a80f409d777d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v50 05/10] nss: pg_strong_random support
This adds support for using the RNG in libnss as a backend for
pg_strong_random.
---
src/port/pg_strong_random.c | 52 +++++++++++++++++++++++++++++++++++--
1 file changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index 07f24c0089..29bcb796c8 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v50-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v50-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 7ab705b22d10663a6363b8a2974728e006b66c59 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v50 04/10] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
6 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 11ca525c6c..f749633ca8 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index 766fed67cc..8b8fad7c86 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 13d664dda0..28a6f44250 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index 5a9a009832..523a77b2c5 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index c061e850fb..0ff0e9a7d6 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2153,8 +2153,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 77192e44c8..a25e657dda 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
--
2.24.3 (Apple Git-128)
v50-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v50-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 1fc3f5b9872d3a4fcbef426a1a69c89dfa253f57 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v50 03/10] nss: Add NSS specific tests
This adds the SSL/Backend/NSS which implements the setup and teardown
required as well as adds the NSS configuration to the existing tests.
The OpenSSL testfiles are reused by repackaging them into NSS databases
in order for the tests to be cross-library. Additional testfiles are
generated by using only the NSS toolchain as well as rudimentary tests
using these.
---
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 +++++++++++++++
src/test/ssl/t/001_ssltests.pl | 458 ++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/003_sslinfo.pl | 12 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 66 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 25 +-
src/test/ssl/t/SSL/Server.pm | 60 ++--
9 files changed, 698 insertions(+), 239 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index b749383348..fa43951d80 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -18,14 +18,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 270f55a58f..a11050e86d 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fbd25a99f1..77192e44c8 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -361,66 +454,86 @@ $node->connect_fails(
# correct client cert in unencrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-der.key'}",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-der.key'),
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-der.key'} sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-der.key') . " sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qclient-encrypted-pem.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -431,49 +544,52 @@ TODO:
# correct client cert in encrypted PEM with empty password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword=''",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword=''",
"certificate authorization fails with correct client cert and empty password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
# correct client cert in encrypted PEM with no password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key'),
"certificate authorization fails with correct client cert and no password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
}
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno =~ s/\s+//g;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno =~ s/\s+//g;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -485,42 +601,47 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key') . " ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client_wrongperms.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client_wrongperms.key'),
"certificate authorization fails because of file permissions",
expected_stderr =>
- qr!\Qprivate key file "$key{'client_wrongperms.key'}" has group or world access\E!
+ qr!\Qclient_wrongperms.key" has group or world access\E!
);
}
# client cert belonging to another user
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -528,16 +649,16 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full succeeds with matching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
expected_stderr =>
qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
@@ -547,15 +668,15 @@ $node->connect_fails(
# Check that connecting with auth-optionverify-ca in pg_hba :
# works, when username doesn't match Common Name
$node->connect_ok(
- "$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=yetanotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=$key{'client.key'} sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb " . sslkey('client.key') . " sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -563,14 +684,15 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key') . " ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 247887bb56..bc30416bc7 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,24 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +43,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +61,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", 'password' => "pass", 'password_enc' => "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -104,7 +115,7 @@ chmod 0600, "$cert_tempdir/client_scram.key"
or die "failed to change permissions on $cert_tempdir/client_scram.key: $!";
$client_tmp_key =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -112,10 +123,10 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
]);
-done_testing($number_of_tests);
+done_testing();
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 6130040283..f3c6db2989 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -12,15 +12,19 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan tests => 13;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'SSL not supported by this build';
}
else
{
- plan tests => 13;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
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..58a1ec3108
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,66 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend get_nss_key);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub get_key
+{
+ return " ";
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 991953bf2c..d2452a0737 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -6,7 +6,7 @@ use Exporter;
use File::Copy;
our @ISA = qw(Exporter);
-our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+our @EXPORT_OK = qw(get_new_openssl_backend);
our (%key);
@@ -70,28 +70,31 @@ sub init
$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
}
-sub get_openssl_key
+sub get_key
{
my $self = $_[0];
my $keyfile = $_[1];
- return $key{$keyfile};
+ return " sslkey=$key{$keyfile}";
}
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 3683862399..5338337d40 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -36,7 +36,7 @@ use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
-use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
use SSL::Backend::NSS qw(get_new_nss_backend);
our ($openssl, $nss, $backend);
@@ -58,9 +58,8 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
- key
+ sslkey
);
# Copy a set of files, taking into account wildcards
@@ -79,12 +78,12 @@ sub copy_files
return;
}
-sub key
+# Return a sslkey construct for use in a connection string.
+sub sslkey
{
- my ($node, $keyfile) = @_;
+ my ($keyfile) = shift;
- return get_openssl_key($keyfile) if (defined($openssl);
- return get_nss_key($keyfile) if (defined($nss));
+ return $backend->get_key($keyfile);
}
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
@@ -168,7 +167,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -193,46 +192,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v50-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v50-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 77dd311e8e4103ad3f3dde72ce92ea7f890d8431 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v50 02/10] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 57 +--------
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 110 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 92 ++++++++++++---
4 files changed, 195 insertions(+), 68 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (77%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 779ab66838..fbd25a99f1 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,39 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes to using keys stored in a temporary path for the rest of
-# the tests. To get the full path for inclusion in connection strings, the
-# %key hash can be interrogated.
-my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
-my %key;
-my @keys = (
- "client.key", "client-revoked.key",
- "client-der.key", "client-encrypted-pem.key",
- "client-encrypted-der.key", "client-dn.key");
-foreach my $keyfile (@keys)
-{
- copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
- or die
- "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
- chmod 0600, "$cert_tempdir/$keyfile"
- or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
- $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
- $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
- or die
- "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
-chmod 0644, "$cert_tempdir/client_wrongperms.key"
- or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
-$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
-$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
#### Set up the server.
note "setting up data directory";
@@ -83,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 1c14bb62e6..247887bb56 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..991953bf2c
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,110 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+
+our (%key);
+
+INIT
+{
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes to using keys stored in a temporary path for the rest of
+ # the tests. To get the full path for inclusion in connection strings, the
+ # %key hash can be interrogated.
+ my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
+ my @keys = (
+ "client.key", "client-revoked.key",
+ "client-der.key", "client-encrypted-pem.key",
+ "client-encrypted-der.key", "client-dn.key");
+ foreach my $keyfile (@keys)
+ {
+ copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
+ or die
+ "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
+ chmod 0600, "$cert_tempdir/$keyfile"
+ or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
+ $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
+ $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
+ or die
+ "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
+ chmod 0644, "$cert_tempdir/client_wrongperms.key"
+ or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
+ $key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
+ $key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+}
+
+sub get_openssl_key
+{
+ my $self = $_[0];
+ my $keyfile = $_[1];
+
+ return $key{$keyfile};
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 77%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 3d7ff40b97..3683862399 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,20 +26,41 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
+ key
);
# Copy a set of files, taking into account wildcards
@@ -58,6 +79,14 @@ sub copy_files
return;
}
+sub key
+{
+ my ($node, $keyfile) = @_;
+
+ return get_openssl_key($keyfile) if (defined($openssl);
+ return get_nss_key($keyfile) if (defined($nss));
+}
+
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
sub configure_test_server_for_ssl
@@ -126,14 +155,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -141,20 +177,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -164,13 +216,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v50-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v50-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From f99c2e42e01db76fffd79fa9ab8bbe51cdbc064c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v50 01/10] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1586 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1200 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3192 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 5196e4797a..d00b4f4b61 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9506,7 +9506,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 7bcf52523b..9d0e30f248 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2752,7 +2752,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2c0b20086e
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1586 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secoidt.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ port->peer_cert_valid = false;
+ port->ssl_in_use = false;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ /*
+ * NSS returns PR_END_OF_FILE_ERROR if the handshake was incomplete,
+ * which for example will happen if the client hasn't been set up with
+ * the correct certificates etc.
+ */
+ if (PR_GetError() == PR_END_OF_FILE_ERROR)
+ ereport(COMMERROR,
+ (errmsg("could not accept SSL connection: EOF detected")));
+ else
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 8ef083200a..cf056f5364 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f736e8d872..e240f82a8a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4442,7 +4442,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4510,6 +4514,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4538,8 +4552,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..10455ecbe5
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..c8388c5450 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6b67a2a318..351520901e 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 427ffabd14..5e908a292e 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -202,7 +202,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b6a6939f0..3aa68a336b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..4592d1969d
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+
+#include <hasht.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ /*
+ * If the system trust module has been loaded we must try to unload it
+ * before closing the context, since it will otherwise fail reporting a
+ * SEC_ERROR_BUSY error.
+ */
+ if (ca_trust != NULL)
+ {
+ if (SECMOD_UnloadUserModule(ca_trust) != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to unload trust module");
+ }
+ else
+ {
+ SECMOD_DestroyModule(ca_trust);
+ ca_trust = NULL;
+ }
+ }
+
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_PK11RELOAD);
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initialize NSS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!certificate_database_has_CA(conn))
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate database does not contain a CA certificate\n"
+ "Either provide a database with a CA or change sslmode to disable server certificate verification.\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy by setting the domestic suite.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ Assert(conn->nss_context);
+ Assert(conn->pr_fd);
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ return 0;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname"));
+ return 0;
+ }
+
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ CERT_DestroyCertificate(server_cert);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index b15d8d137c..d76eb4806f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..186e0a28f4 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 490458adef..ab0da13700 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
On Wed, 2021-12-15 at 23:10 +0100, Daniel Gustafsson wrote:
On 30 Nov 2021, at 20:03, Jacob Champion <pchampion@vmware.com> wrote:
On Mon, 2021-09-27 at 15:44 +0200, Daniel Gustafsson wrote:
Speaking of IP addresses in SANs, it doesn't look like our OpenSSL
backend can handle those. That's a separate conversation, but I might
take a look at a patch for next commitfest.Please do.
Didn't get around to it for November, but I'm putting the finishing
touches on that now.Cool, thanks!
Done and registered in Commitfest.
Yeah, that's clearly bogus. I followed the bouncing ball reading NSS code and
from what I can tell the comment is correct. I removed the dead code, only
realizing after the fact that I might cause conflict with your tree doing so,
in that case sorry.
No worries, there weren't any issues with the rebase.
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.
Thanks!
--Jacob
On Wed, Dec 15, 2021 at 5:05 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 25 Nov 2021, at 14:39, Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Wed, Nov 24, 2021 at 8:49 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:On Wed, Nov 24, 2021 at 8:46 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:I don't know enough about NSS to know if this is problematic or not
but if I try verify-full without having the root CA in the certificate
store I get:$ /usr/pgsql-15/bin/psql "host=localhost sslmode=verify-full user=postgres"
psql: error: SSL error: Issuer certificate is invalid.
unable to shut down NSS context: NSS could not shutdown. Objects are
still in use.Fixed.
Something is strange with ssl downgrading and a bad ssldatabase
[postgres@11cdfa30f763 ~]$ /usr/pgsql-15/bin/psql "ssldatabase=oops
sslcert=client_cert host=localhost"
Password for user postgres:<freezes here>
Also fixed.
On the server side:
2021-11-25 01:52:01.984 UTC [269] LOG: unable to handshake:
Encountered end of file (PR_END_OF_FILE_ERROR)This is normal and expected, but to make it easier on users I've changed this
error message to be aligned with the OpenSSL implementation.Other than that and I still haven't tested --with-llvm I've gotten
everything working, including with an openssl client. Attached is a
dockerfile that gets to the point where a client can connect with
clientcert=verify-full. I've removed some of the old cruft and
debugging from the previous versions.Very cool, thanks! I've been unable to reproduce any issues with llvm but I'll
keep poking at that. A new version will be posted shortly with the above and a
few more fixes.
For v50 this change was required for an llvm build to succeed on my
Fedora system:
diff --git a/configure b/configure
index 25388a75a2..62d554806a 100755
--- a/configure
+++ b/configure
@@ -13276,6 +13276,7 @@ fi
LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
$as_echo "#define USE_NSS 1" >>confdefs.h
I'm not certain why configure didn't already have that, configure.ac
appears to, but nonetheless it builds, all tests succeed, and a quick
tire kicking looks good.
Thank you.
Hi,
On Wed, Dec 15, 2021 at 11:10:14PM +0100, Daniel Gustafsson wrote:
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.
The cfbot reports that the patchset doesn't apply anymore:
http://cfbot.cputube.org/patch_36_3138.log
=== Applying patches on top of PostgreSQL commit ID 74527c3e022d3ace648340b79a6ddec3419f6732 ===
[...]
=== applying patch ./v50-0010-nss-Build-infrastructure.patch
patching file configure
patching file configure.ac
Hunk #3 succeeded at 1566 (offset 1 line).
Hunk #4 succeeded at 2366 (offset 1 line).
Hunk #5 succeeded at 2379 (offset 1 line).
patching file src/backend/libpq/Makefile
patching file src/common/Makefile
patching file src/include/pg_config.h.in
Hunk #3 succeeded at 926 (offset 3 lines).
patching file src/interfaces/libpq/Makefile
patching file src/tools/msvc/Install.pm
Hunk #1 FAILED at 440.
1 out of 1 hunk FAILED -- saving rejects to file src/tools/msvc/Install.pm.rej
Could you send a rebased version, possibly with an updated configure as
reported by Joshua? In the meantime I will switch the entry to Waitinng on
Author.
On 15 Jan 2022, at 05:42, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Wed, Dec 15, 2021 at 11:10:14PM +0100, Daniel Gustafsson wrote:I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.The cfbot reports that the patchset doesn't apply anymore:
Fixed, as well as rebased and fixed up on top of the recent cryptohash error
reporting functionality to support that on par with the OpenSSL backend.
..possibly with an updated configure as reported by Joshua?
I must've fat-fingered the "git add -p" for v50 as the fix was in configure.ac
but not configure. Fixed now.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v51-0011-NSS-experimental-support-for-NSS-in-CI.patchapplication/octet-stream; name=v51-0011-NSS-experimental-support-for-NSS-in-CI.patch; x-unix-mode=0644Download
From ece57231cad3da0e5afa2eae924c8938fb44a3ec Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 18 Dec 2021 16:06:56 +0100
Subject: [PATCH v51 11/11] NSS: experimental support for NSS in CI
---
.cirrus.yml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index 677bdf0e65..0b0866570a 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,6 @@ LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
--with-perl
--with-python
--with-selinux
- --with-ssl=openssl
--with-systemd
--with-tcl --with-tclconfig=/usr/lib/tcl8.6/
--with-uuid=ossp
@@ -194,6 +193,81 @@ task:
./configure \
--enable-cassert --enable-debug --enable-tap-tests \
--enable-nls \
+ --with-ssl=openssl \
+ \
+ ${LINUX_CONFIGURE_FEATURES} \
+ \
+ CC="ccache gcc" \
+ CXX="ccache g++" \
+ CLANG="ccache clang" \
+ CFLAGS="-O0 -ggdb" \
+ CXXFLAGS="-O0 -ggdb"
+ EOF
+ build_script: su postgres -c "make -s -j${BUILD_JOBS} world-bin"
+ upload_caches: ccache
+
+ test_world_script: |
+ su postgres <<-EOF
+ ulimit -c unlimited # default is 0
+ make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
+ EOF
+
+ on_failure:
+ <<: *on_failure
+ cores_script: src/tools/ci/cores_backtrace.sh linux /tmp/cores
+
+
+task:
+ name: Linux - Debian Bullseye (nss)
+
+ env:
+ CPUS: 4
+ BUILD_JOBS: 4
+ TEST_JOBS: 8 # experimentally derived to be a decent choice
+
+ CCACHE_DIR: /tmp/ccache_dir
+ DEBUGINFOD_URLS: "https://debuginfod.debian.net"
+
+ LINUX_CONFIGURE_FEATURES: *LINUX_CONFIGURE_FEATURES
+
+ only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*linux.*'
+
+ compute_engine_instance:
+ image_project: $IMAGE_PROJECT
+ image: family/pg-ci-bullseye
+ platform: linux
+ cpu: $CPUS
+ memory: 2G
+
+ ccache_cache:
+ folder: ${CCACHE_DIR}
+
+ sysinfo_script: |
+ id
+ uname -a
+ cat /proc/cmdline
+ ulimit -a -H && ulimit -a -S
+ export
+ create_user_script: |
+ useradd -m postgres
+ chown -R postgres:postgres .
+ mkdir -p ${CCACHE_DIR}
+ chown -R postgres:postgres ${CCACHE_DIR}
+ echo '* - memlock 134217728' > /etc/security/limits.d/postgres.conf
+ su postgres -c "ulimit -l -H && ulimit -l -S"
+ setup_cores_script: |
+ mkdir -m 770 /tmp/cores
+ chown root:postgres /tmp/cores
+ sysctl kernel.core_pattern='/tmp/cores/%e-%s-%p.core'
+ install_script: |
+ DEBIAN_FRONTEND=noninteractive apt-get --yes install libnss3 libnss3-dev libnss3-tools libnspr4 libnspr4-dev
+
+ configure_script: |
+ su postgres <<-EOF
+ ./configure \
+ --enable-cassert --enable-debug --enable-tap-tests \
+ --enable-nls \
+ --with-ssl=nss \
\
${LINUX_CONFIGURE_FEATURES} \
\
@@ -209,6 +283,7 @@ task:
test_world_script: |
su postgres <<-EOF
ulimit -c unlimited # default is 0
+ make -C src/test/ssl/ nssfiles
make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
EOF
--
2.24.3 (Apple Git-128)
v51-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v51-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From e8e7105a57dd316ba588ace37da61896df189d30 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v51 10/11] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 376 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 6 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 535 insertions(+), 13 deletions(-)
diff --git a/configure b/configure
index 3f2aea0d7d..a47685dcd5 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12931,8 +12933,356 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13903,6 +14253,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18670,6 +19037,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18702,7 +19072,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 95287705f6..b4bb1b14db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1270,7 +1270,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1304,8 +1304,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1496,6 +1566,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2293,6 +2366,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
elif test x"$cross_compiling" = x"yes"; then
@@ -2304,7 +2379,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 31c0dd366d..501009ce2a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9d9bd6b9ef..25a31c0eee 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -917,6 +926,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 844c95d47d..fdf32dbbca 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 8de79c618c..facc454e3d 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,8 +440,10 @@ 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 "pgcrypto" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
+ next if ($d eq "pgcrypto" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ec3546d0c0..0bf67ff6df 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,6 +138,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');
@@ -203,12 +209,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -266,12 +279,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -443,7 +463,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e47c2d648c..8c45e599ec 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -496,6 +499,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -557,6 +561,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v51-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v51-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 9bb08f0994e9da462760f1dfed468ebe9aa188f0 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v51 09/11] 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 | 308 ++++++++++++++++++++++++++++++++++++
1 file changed, 308 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..fff63d0f1b
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,308 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+ const char *errreason;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ ctx->errreason = NULL;
+ ctx->type = type;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ if (ctx->errreason != NULL)
+ ereport(ERROR,
+ (errmsg("NSS failure: %s", ctx->errreason)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
+
+/*
+ * pg_cryptohash_error
+ *
+ * Returns a static string providing details about an error that happened
+ * during a computation.
+ */
+const char *
+pg_cryptohash_error(pg_cryptohash_ctx *ctx)
+{
+ if (!ctx)
+ return _("out of memory");
+
+ if (ctx->errreason)
+ return ctx->errreason;
+
+ /*
+ * PG_ErrorToString never returns NULL, and all error codepaths invoke it
+ * so if we end up here we either don't have any error at all or something
+ * very bad happened.
+ */
+ return _("unknown failure");
+}
--
2.24.3 (Apple Git-128)
v51-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v51-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From fb33fa42874f81a136ed91f4af377efda878c6b1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v51 08/11] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++-
src/test/ssl/sslfiles_nss.mk | 12 +++++-
src/test/ssl/t/003_sslinfo.pl | 80 +++++++++++++++++------------------
4 files changed, 93 insertions(+), 43 deletions(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
index adb7363d63..3b4e2494a2 100644
--- a/src/test/ssl/sslfiles_nss.mk
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -43,7 +43,8 @@ NSSFILES := ssl/nss/client_ca.crt.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
+ ssl/nss/native_client-root.db \
+ ssl/nss/client_ext.crt__client_ext.key.db
nssfiles: $(NSSFILES)
@@ -168,6 +169,15 @@ ssl/nss/client.crt__client.key.db: ssl/client.crt
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 ''
+ssl/nss/client_ext.crt__client_ext.key.db: ssl/client_ext.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client_ext.crt -i ssl/client_ext.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client_ext.pfx -inkey ssl/client_ext.key -in ssl/client_ext.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client_ext.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) $@
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 4dc457e1b6..7d9800474d 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,18 +14,8 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} eq 'openssl')
-{
- plan tests => 13;
-}
-elsif ($ENV{with_ssl} eq 'nss')
-{
- plan skip_all => 'SSL not supported by this build';
-}
-else
-{
- plan skip_all => 'SSL not supported by this build';
-}
+plan skip_all => 'SSL not supported by this build'
+ unless (($ENV{with_ssl} eq 'openssl') || ($ENV{with_ssl} eq 'nss'));
#### Some configuration
@@ -68,10 +58,11 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
# We aren't using any CRL's in this suite so we can keep using server-revoked
# as server certificate for simple client.crt connection much like how the
# 001 test does.
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR " .
+ "ssldatabase=ssl/nss/client_ext.crt__client_ext.key.db " .
"user=ssltestuser sslcert=ssl/client_ext.crt sslkey=$client_tmp_key";
# Make sure we can connect even though previous test suites have established this
@@ -110,32 +101,39 @@ $result = $node->safe_psql("certdb",
connstr => $common_connstr);
is($result, 't', "ssl_client_serial() compared with pg_stat_ssl");
-# Must not use safe_psql since we expect an error here
-$result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
- connstr => $common_connstr);
-is($result, '3', "ssl_client_dn_field() for an invalid field");
-
-$result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
- connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
- "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
-is($result, '', "ssl_client_dn_field() for connection without cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_client_dn_field() for commonName");
-
-$result = $node->safe_psql("certdb",
- "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_dn() for connection with cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_field() for commonName");
+SKIP:
+{
+ skip "Functionality not implemented with NSS", 6 if ($ENV{with_ssl} eq 'nss');
+
+ # Must not use safe_psql since we expect an error here
+ $result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
+ connstr => $common_connstr);
+ is($result, '3', "ssl_client_dn_field() for an invalid field");
+
+ $result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
+ connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
+ "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
+ is($result, '', "ssl_client_dn_field() for connection without cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_client_dn_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_dn() for connection with cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
+ connstr => $common_connstr);
+ is($result, 'CA:FALSE|t', 'extract extension from cert');
+}
-$result = $node->safe_psql("certdb",
- "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
- connstr => $common_connstr);
-is($result, 'CA:FALSE|t', 'extract extension from cert');
+done_testing();
--
2.24.3 (Apple Git-128)
v51-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v51-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 574c08fc33000082570f29c1d3b7fe8e0e260369 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v51 07/11] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 62 ++
contrib/pgcrypto/expected/cast5_2.out | 33 +
contrib/pgcrypto/expected/pgp-decrypt_2.out | 415 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 200 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 48 ++
contrib/pgcrypto/nss.c | 601 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 10 +-
12 files changed, 2060 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..5b8b55b84f
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,62 @@
+--
+-- Blowfish cipher
+--
+-- some standard Blowfish testvalues
+SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5',
+ '\x37363534333231204e6f77206973207468652074696d6520666f7220',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc',
+ '\x37363534333231204e6f77206973207468652074696d6520666f722000',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encrypt('\xfedcba9876543210',
+ '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff',
+ 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- empty data
+select encrypt('', 'foo', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..5e829609b0
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,33 @@
+--
+-- Cast5 cipher
+--
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 80 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 40 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- cbc
+-- empty data
+select encrypt('', 'foo', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..6ff774b190
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,415 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \xda39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- Checking CRLF
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1');
+ digest
+--------------------------------------------
+ \x9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1');
+ digest
+--------------------------------------------
+ \x7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..168dc9bafa
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,200 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key');
+ pgp_sym_decrypt_bytea
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1') as result,
+ digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+ result | expect
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..a5dd6d092c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,48 @@
+--
+-- PGP Public Key Encryption
+--
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select encode(pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey)), 'escape')
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..590d0a8559
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,601 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..2e3fc914e1 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,10 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal>, and
+ <literal>blowfish</literal> are only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1160,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1180,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v51-0006-nss-Documentation.patchapplication/octet-stream; name=v51-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 6acfb161f80b7ecb40183beef80ec5b69afbdf09 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v51 06/11] 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 | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4cd9818acf..630390bd7e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index d38f9bc916..db44c415d0 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 14f35d37f6..67dd923fce 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL server certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8716,6 +8889,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8742,6 +8921,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f77ed24204..132e8cbf80 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v51-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v51-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 4bedc5391b92a85289752f90530a78eb3567607b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v51 05/11] 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 be589c9d0b..2fec8e3bdf 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v51-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v51-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From a5539a282f0254cea2c33d8404051e48100acfec Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v51 04/11] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
6 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 5d56455857..29110af614 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index d8995b1ae5..808ed8991b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 2b539d2402..0c0b7459fb 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index cdc4e1e5c8..39d8010aee 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 7af0f8db13..88f5e2746f 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2153,8 +2153,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 11127d9fed..48a42ec554 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
--
2.24.3 (Apple Git-128)
v51-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v51-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From a5a60b1e9e69da9129d51638321c5027d2c70302 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v51 03/11] 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 | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 +++++++++++++++
src/test/ssl/t/001_ssltests.pl | 458 ++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/003_sslinfo.pl | 12 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 66 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 21 +-
src/test/ssl/t/SSL/Server.pm | 57 +---
9 files changed, 695 insertions(+), 235 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 12b02eb422..b22cb51556 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -18,14 +18,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..065e08a104 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 8d6b9dfbdc..11127d9fed 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -361,66 +454,86 @@ $node->connect_fails(
# correct client cert in unencrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-der.key'}",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-der_tmp.key'),
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-der.key'} sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-der.key') . " sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": bad decrypt\E!
+ qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -431,49 +544,52 @@ TODO:
# correct client cert in encrypted PEM with empty password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword=''",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword=''",
"certificate authorization fails with correct client cert and empty password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
# correct client cert in encrypted PEM with no password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key'),
"certificate authorization fails with correct client cert and no password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
}
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno =~ s/\s+//g;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno =~ s/\s+//g;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -485,42 +601,47 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key') . " ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client_wrongperms.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client_wrongperms.key'),
"certificate authorization fails because of file permissions",
expected_stderr =>
- qr!\Qprivate key file "$key{'client_wrongperms.key'}" has group or world access\E!
+ qr!\Qprivate key file ".*client_wrongperms.key" has group or world access\E!
);
}
# client cert belonging to another user
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -528,16 +649,16 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full succeeds with matching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
expected_stderr =>
qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
@@ -547,15 +668,15 @@ $node->connect_fails(
# Check that connecting with auth-optionverify-ca in pg_hba :
# works, when username doesn't match Common Name
$node->connect_ok(
- "$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=yetanotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=$key{'client.key'} sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb " . sslkey('client.key') . " sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -563,14 +684,15 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key') . " ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 56c5f88d5f..fd3c881040 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,24 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +43,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +61,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", 'password' => "pass", 'password_enc' => "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -104,7 +115,7 @@ chmod 0600, "$cert_tempdir/client_scram.key"
or die "failed to change permissions on $cert_tempdir/client_scram.key: $!";
$client_tmp_key =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -112,10 +123,10 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
]);
-done_testing($number_of_tests);
+done_testing();
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 8c760b39db..4dc457e1b6 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -12,15 +12,19 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan tests => 13;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'SSL not supported by this build';
}
else
{
- plan tests => 13;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
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..e2b9b8cfc9
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,66 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend get_nss_key);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub get_nss_key
+{
+ return " ";
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 991953bf2c..6da919bc5f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -75,23 +75,26 @@ sub get_openssl_key
my $self = $_[0];
my $keyfile = $_[1];
- return $key{$keyfile};
+ return " sslkey=$key{$keyfile}";
}
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 9275876250..364b2173fa 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -37,7 +37,7 @@ use File::Basename;
use File::Copy;
use Test::More;
use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
-use SSL::Backend::NSS qw(get_new_nss_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend get_nss_key);
our ($openssl, $nss, $backend);
@@ -58,9 +58,8 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
- key
+ sslkey
);
# Copy a set of files, taking into account wildcards
@@ -79,11 +78,12 @@ sub copy_files
return;
}
-sub key
+# Return a sslkey construct for use in a connection string.
+sub sslkey
{
my ($node, $keyfile) = @_;
- return get_openssl_key($keyfile) if (defined($openssl);
+ return get_openssl_key($keyfile) if (defined($openssl));
return get_nss_key($keyfile) if (defined($nss));
}
@@ -168,7 +168,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -193,46 +193,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v51-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v51-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From b533cef163eda5a67de37c93d54ef75fcf6a6443 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v51 02/11] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 57 +--------
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 110 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 92 ++++++++++++---
4 files changed, 195 insertions(+), 68 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (77%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1fb15ce80..8d6b9dfbdc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,39 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes to using keys stored in a temporary path for the rest of
-# the tests. To get the full path for inclusion in connection strings, the
-# %key hash can be interrogated.
-my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
-my %key;
-my @keys = (
- "client.key", "client-revoked.key",
- "client-der.key", "client-encrypted-pem.key",
- "client-encrypted-der.key", "client-dn.key");
-foreach my $keyfile (@keys)
-{
- copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
- or die
- "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
- chmod 0600, "$cert_tempdir/$keyfile"
- or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
- $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
- $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
- or die
- "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
-chmod 0644, "$cert_tempdir/client_wrongperms.key"
- or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
-$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
-$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
#### Set up the server.
note "setting up data directory";
@@ -83,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 86312be88c..56c5f88d5f 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..991953bf2c
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,110 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+
+our (%key);
+
+INIT
+{
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes to using keys stored in a temporary path for the rest of
+ # the tests. To get the full path for inclusion in connection strings, the
+ # %key hash can be interrogated.
+ my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
+ my @keys = (
+ "client.key", "client-revoked.key",
+ "client-der.key", "client-encrypted-pem.key",
+ "client-encrypted-der.key", "client-dn.key");
+ foreach my $keyfile (@keys)
+ {
+ copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
+ or die
+ "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
+ chmod 0600, "$cert_tempdir/$keyfile"
+ or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
+ $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
+ $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
+ or die
+ "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
+ chmod 0644, "$cert_tempdir/client_wrongperms.key"
+ or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
+ $key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
+ $key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+}
+
+sub get_openssl_key
+{
+ my $self = $_[0];
+ my $keyfile = $_[1];
+
+ return $key{$keyfile};
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 77%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c85c6fd997..9275876250 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,20 +26,41 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
+ key
);
# Copy a set of files, taking into account wildcards
@@ -58,6 +79,14 @@ sub copy_files
return;
}
+sub key
+{
+ my ($node, $keyfile) = @_;
+
+ return get_openssl_key($keyfile) if (defined($openssl);
+ return get_nss_key($keyfile) if (defined($nss));
+}
+
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
sub configure_test_server_for_ssl
@@ -126,14 +155,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -141,20 +177,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -164,13 +216,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v51-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v51-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 02f752e497c26cdada077c941b6ff6a88de1c78f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v51 01/11] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1586 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1200 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3192 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7d6f7d9e3d..8582d9f98b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9509,7 +9509,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f3135..eda595624f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2754,7 +2754,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2c0b20086e
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1586 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secoidt.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ port->peer_cert_valid = false;
+ port->ssl_in_use = false;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ /*
+ * NSS returns PR_END_OF_FILE_ERROR if the handshake was incomplete,
+ * which for example will happen if the client hasn't been set up with
+ * the correct certificates etc.
+ */
+ if (PR_GetError() == PR_END_OF_FILE_ERROR)
+ ereport(COMMERROR,
+ (errmsg("could not accept SSL connection: EOF detected")));
+ else
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index a05f67afb5..1cd1a724d0 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c94f09c64..9f0e935820 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4452,7 +4452,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4520,6 +4524,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4548,8 +4562,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..10455ecbe5
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index dd3e5efba3..d102bd56b5 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index f0786e08b4..e37b1b67eb 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d2e3e3a57..c2a0694669 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -202,7 +202,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 5fc16be849..a93e7c0afc 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..4592d1969d
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+
+#include <hasht.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ /*
+ * If the system trust module has been loaded we must try to unload it
+ * before closing the context, since it will otherwise fail reporting a
+ * SEC_ERROR_BUSY error.
+ */
+ if (ca_trust != NULL)
+ {
+ if (SECMOD_UnloadUserModule(ca_trust) != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to unload trust module");
+ }
+ else
+ {
+ SECMOD_DestroyModule(ca_trust);
+ ca_trust = NULL;
+ }
+ }
+
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_PK11RELOAD);
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initialize NSS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!certificate_database_has_CA(conn))
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate database does not contain a CA certificate\n"
+ "Either provide a database with a CA or change sslmode to disable server certificate verification.\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy by setting the domestic suite.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ Assert(conn->nss_context);
+ Assert(conn->pr_fd);
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ return 0;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname"));
+ return 0;
+ }
+
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ CERT_DestroyCertificate(server_cert);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 0b998e254d..b7dcf570af 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 20eb855abc..119947871b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fcce13843e..ea95f209cb 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
Hi,
On Mon, Jan 17, 2022 at 03:09:11PM +0100, Daniel Gustafsson wrote:
I must've fat-fingered the "git add -p" for v50 as the fix was in configure.ac
but not configure. Fixed now.
Thanks! Apparently this version now fails on all OS, e.g.:
https://cirrus-ci.com/task/4643868095283200
[22:17:39.965] # Failed test 'certificate authorization succeeds with correct client cert in PEM format'
[22:17:39.965] # at t/001_ssltests.pl line 456.
[22:17:39.965] # got: '2'
[22:17:39.965] # expected: '0'
[22:17:39.965]
[22:17:39.965] # Failed test 'certificate authorization succeeds with correct client cert in PEM format: no stderr'
[22:17:39.965] # at t/001_ssltests.pl line 456.
[22:17:39.965] # got: 'psql: error: connection to server at "127.0.0.1", port 50023 failed: certificate present, but not private key file "/home/postgres/.postgresql/postgresql.key"'
[22:17:39.965] # expected: ''
[22:17:39.965]
[22:17:39.965] # Failed test 'certificate authorization succeeds with correct client cert in DER format'
[22:17:39.965] # at t/001_ssltests.pl line 475.
[22:17:39.965] # got: '2'
[22:17:39.965] # expected: '0'
[...]
On 18 Jan 2022, at 07:36, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Mon, Jan 17, 2022 at 03:09:11PM +0100, Daniel Gustafsson wrote:
I must've fat-fingered the "git add -p" for v50 as the fix was in configure.ac
but not configure. Fixed now.Thanks! Apparently this version now fails on all OS, e.g.:
Fixed, I had made a mistake in the OpenSSL.pm testcode and failed to catch it
in testing.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v52-0011-NSS-experimental-support-for-NSS-in-CI.patchapplication/octet-stream; name=v52-0011-NSS-experimental-support-for-NSS-in-CI.patch; x-unix-mode=0644Download
From a93e22e99a864da629829af3c5d5eab703971948 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 18 Dec 2021 16:06:56 +0100
Subject: [PATCH v52 11/11] NSS: experimental support for NSS in CI
---
.cirrus.yml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index 677bdf0e65..0b0866570a 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,6 @@ LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
--with-perl
--with-python
--with-selinux
- --with-ssl=openssl
--with-systemd
--with-tcl --with-tclconfig=/usr/lib/tcl8.6/
--with-uuid=ossp
@@ -194,6 +193,81 @@ task:
./configure \
--enable-cassert --enable-debug --enable-tap-tests \
--enable-nls \
+ --with-ssl=openssl \
+ \
+ ${LINUX_CONFIGURE_FEATURES} \
+ \
+ CC="ccache gcc" \
+ CXX="ccache g++" \
+ CLANG="ccache clang" \
+ CFLAGS="-O0 -ggdb" \
+ CXXFLAGS="-O0 -ggdb"
+ EOF
+ build_script: su postgres -c "make -s -j${BUILD_JOBS} world-bin"
+ upload_caches: ccache
+
+ test_world_script: |
+ su postgres <<-EOF
+ ulimit -c unlimited # default is 0
+ make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
+ EOF
+
+ on_failure:
+ <<: *on_failure
+ cores_script: src/tools/ci/cores_backtrace.sh linux /tmp/cores
+
+
+task:
+ name: Linux - Debian Bullseye (nss)
+
+ env:
+ CPUS: 4
+ BUILD_JOBS: 4
+ TEST_JOBS: 8 # experimentally derived to be a decent choice
+
+ CCACHE_DIR: /tmp/ccache_dir
+ DEBUGINFOD_URLS: "https://debuginfod.debian.net"
+
+ LINUX_CONFIGURE_FEATURES: *LINUX_CONFIGURE_FEATURES
+
+ only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*linux.*'
+
+ compute_engine_instance:
+ image_project: $IMAGE_PROJECT
+ image: family/pg-ci-bullseye
+ platform: linux
+ cpu: $CPUS
+ memory: 2G
+
+ ccache_cache:
+ folder: ${CCACHE_DIR}
+
+ sysinfo_script: |
+ id
+ uname -a
+ cat /proc/cmdline
+ ulimit -a -H && ulimit -a -S
+ export
+ create_user_script: |
+ useradd -m postgres
+ chown -R postgres:postgres .
+ mkdir -p ${CCACHE_DIR}
+ chown -R postgres:postgres ${CCACHE_DIR}
+ echo '* - memlock 134217728' > /etc/security/limits.d/postgres.conf
+ su postgres -c "ulimit -l -H && ulimit -l -S"
+ setup_cores_script: |
+ mkdir -m 770 /tmp/cores
+ chown root:postgres /tmp/cores
+ sysctl kernel.core_pattern='/tmp/cores/%e-%s-%p.core'
+ install_script: |
+ DEBIAN_FRONTEND=noninteractive apt-get --yes install libnss3 libnss3-dev libnss3-tools libnspr4 libnspr4-dev
+
+ configure_script: |
+ su postgres <<-EOF
+ ./configure \
+ --enable-cassert --enable-debug --enable-tap-tests \
+ --enable-nls \
+ --with-ssl=nss \
\
${LINUX_CONFIGURE_FEATURES} \
\
@@ -209,6 +283,7 @@ task:
test_world_script: |
su postgres <<-EOF
ulimit -c unlimited # default is 0
+ make -C src/test/ssl/ nssfiles
make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
EOF
--
2.24.3 (Apple Git-128)
v52-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v52-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From 5079ce8a677074b93ef1f118d535c6dee4ce64f9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v52 10/11] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 376 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 6 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
9 files changed, 535 insertions(+), 13 deletions(-)
diff --git a/configure b/configure
index 9c856cb1d5..4176de673b 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12931,8 +12933,356 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13903,6 +14253,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18670,6 +19037,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18702,7 +19072,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 95287705f6..b4bb1b14db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1270,7 +1270,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1304,8 +1304,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1496,6 +1566,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2293,6 +2366,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
elif test x"$cross_compiling" = x"yes"; then
@@ -2304,7 +2379,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 31c0dd366d..501009ce2a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9d9bd6b9ef..25a31c0eee 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -917,6 +926,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 844c95d47d..fdf32dbbca 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 8de79c618c..facc454e3d 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,8 +440,10 @@ 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 "pgcrypto" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
+ next if ($d eq "pgcrypto" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ec3546d0c0..0bf67ff6df 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,6 +138,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');
@@ -203,12 +209,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -266,12 +279,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -443,7 +463,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e47c2d648c..8c45e599ec 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -496,6 +499,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -557,6 +561,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v52-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v52-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 1f35b88b6812ab904de7039bee5316bca13de27c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v52 09/11] 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 | 308 ++++++++++++++++++++++++++++++++++++
1 file changed, 308 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..fff63d0f1b
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,308 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ * Set of wrapper routines on top of NSS to support cryptographic
+ * hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+ const char *errreason;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ ctx->errreason = NULL;
+ ctx->type = type;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ if (ctx->errreason != NULL)
+ ereport(ERROR,
+ (errmsg("NSS failure: %s", ctx->errreason)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
+
+/*
+ * pg_cryptohash_error
+ *
+ * Returns a static string providing details about an error that happened
+ * during a computation.
+ */
+const char *
+pg_cryptohash_error(pg_cryptohash_ctx *ctx)
+{
+ if (!ctx)
+ return _("out of memory");
+
+ if (ctx->errreason)
+ return ctx->errreason;
+
+ /*
+ * PG_ErrorToString never returns NULL, and all error codepaths invoke it
+ * so if we end up here we either don't have any error at all or something
+ * very bad happened.
+ */
+ return _("unknown failure");
+}
--
2.24.3 (Apple Git-128)
v52-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v52-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 506b7b70cd1eaf4fac621de99f0db1dfad4e3f07 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v52 08/11] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 ++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++-
src/test/ssl/sslfiles_nss.mk | 12 +++++-
src/test/ssl/t/003_sslinfo.pl | 80 +++++++++++++++++------------------
4 files changed, 93 insertions(+), 43 deletions(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
index adb7363d63..3b4e2494a2 100644
--- a/src/test/ssl/sslfiles_nss.mk
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -43,7 +43,8 @@ NSSFILES := ssl/nss/client_ca.crt.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
+ ssl/nss/native_client-root.db \
+ ssl/nss/client_ext.crt__client_ext.key.db
nssfiles: $(NSSFILES)
@@ -168,6 +169,15 @@ ssl/nss/client.crt__client.key.db: ssl/client.crt
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 ''
+ssl/nss/client_ext.crt__client_ext.key.db: ssl/client_ext.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client_ext.crt -i ssl/client_ext.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client_ext.pfx -inkey ssl/client_ext.key -in ssl/client_ext.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client_ext.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) $@
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 4dc457e1b6..7d9800474d 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,18 +14,8 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} eq 'openssl')
-{
- plan tests => 13;
-}
-elsif ($ENV{with_ssl} eq 'nss')
-{
- plan skip_all => 'SSL not supported by this build';
-}
-else
-{
- plan skip_all => 'SSL not supported by this build';
-}
+plan skip_all => 'SSL not supported by this build'
+ unless (($ENV{with_ssl} eq 'openssl') || ($ENV{with_ssl} eq 'nss'));
#### Some configuration
@@ -68,10 +58,11 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
# We aren't using any CRL's in this suite so we can keep using server-revoked
# as server certificate for simple client.crt connection much like how the
# 001 test does.
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR " .
+ "ssldatabase=ssl/nss/client_ext.crt__client_ext.key.db " .
"user=ssltestuser sslcert=ssl/client_ext.crt sslkey=$client_tmp_key";
# Make sure we can connect even though previous test suites have established this
@@ -110,32 +101,39 @@ $result = $node->safe_psql("certdb",
connstr => $common_connstr);
is($result, 't', "ssl_client_serial() compared with pg_stat_ssl");
-# Must not use safe_psql since we expect an error here
-$result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
- connstr => $common_connstr);
-is($result, '3', "ssl_client_dn_field() for an invalid field");
-
-$result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
- connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
- "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
-is($result, '', "ssl_client_dn_field() for connection without cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_client_dn_field() for commonName");
-
-$result = $node->safe_psql("certdb",
- "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_dn() for connection with cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_field() for commonName");
+SKIP:
+{
+ skip "Functionality not implemented with NSS", 6 if ($ENV{with_ssl} eq 'nss');
+
+ # Must not use safe_psql since we expect an error here
+ $result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
+ connstr => $common_connstr);
+ is($result, '3', "ssl_client_dn_field() for an invalid field");
+
+ $result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
+ connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
+ "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
+ is($result, '', "ssl_client_dn_field() for connection without cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_client_dn_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_dn() for connection with cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
+ connstr => $common_connstr);
+ is($result, 'CA:FALSE|t', 'extract extension from cert');
+}
-$result = $node->safe_psql("certdb",
- "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
- connstr => $common_connstr);
-is($result, 'CA:FALSE|t', 'extract extension from cert');
+done_testing();
--
2.24.3 (Apple Git-128)
v52-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v52-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From a12769bd793a8e073125c3b3a176b355335646bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v52 07/11] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 62 ++
contrib/pgcrypto/expected/cast5_2.out | 33 +
contrib/pgcrypto/expected/pgp-decrypt_2.out | 415 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 200 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 48 ++
contrib/pgcrypto/nss.c | 601 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 10 +-
12 files changed, 2060 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..5b8b55b84f
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,62 @@
+--
+-- Blowfish cipher
+--
+-- some standard Blowfish testvalues
+SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5',
+ '\x37363534333231204e6f77206973207468652074696d6520666f7220',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc',
+ '\x37363534333231204e6f77206973207468652074696d6520666f722000',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encrypt('\xfedcba9876543210',
+ '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff',
+ 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- empty data
+select encrypt('', 'foo', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..5e829609b0
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,33 @@
+--
+-- Cast5 cipher
+--
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 80 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 40 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- cbc
+-- empty data
+select encrypt('', 'foo', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..6ff774b190
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,415 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \xda39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- Checking CRLF
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1');
+ digest
+--------------------------------------------
+ \x9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1');
+ digest
+--------------------------------------------
+ \x7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..168dc9bafa
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,200 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key');
+ pgp_sym_decrypt_bytea
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1') as result,
+ digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+ result | expect
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY
+05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz
+2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98
+cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN
+SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4
+18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG
+7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt
+q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh
+uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk2wAAoJCUNy6awTkw
+XfbLbpqh0fvDst7jDLa0I0VsZ2FtYWwgNDA5NiA8dGVzdDQwOTZAZXhhbXBsZS5v
+cmc+iF4EExECAB4FAkLII7wCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQY/h1
+9j9ndKABAwCeNEOVK87EzXYbtxYBsnjrUI948NIAn2+f3BXiBFDV5NvqPwIZ0m77
+Fwy4nQRMBELIJHYQEACUGy15YHCf46vl4RLo2++/3nn7RSTmzSEWA/X5iexxSF21
+HJJxKW7PnER/564+XRkmNzk94wlzlLb3nLAqF/wqWVi9KKg4wImbTUhYrpcORmr+
+IB2JFioCnUqPtq5GrxD6iiFR7zHl2ibRk3e7/wJi/z/E+4JbPd6OVhQTi7T2w71q
+mikctlOUwIZdZWojNLxgC0VY70goJxsPYBaUp12ztwH3p1Eow82bo3xix7VOOIsR
+0kvw+A3qYhd2GmC7DeAgamUb+hWzGQ+Vtw5yWOJEtZB9DM3XBdfU5XGd4rUwnBiL
+dbTCn/OJnHD6KnTMx+77pgRk3hRJcuQH6fW3qyXIq6odn85bpzdBktNs+khlp402
+w5ZDQ8bsT0gW6XKTcg946WAFKksEbGf/uc5M3GxrQ5r8vbCeZQF5MRqCKENbRFV/
+yzh54djUByvKLq1Tp6+zEtJ6M7LCE2SeRF6rR4kRI4mBLj8mYlWSiRy1rmGGhfp+
+NM55897CblxUa3h4R2270XZWFFOZiVfl2QS4Crkt9zXaRnjCJ4JwnP/SEOjmW+os
+XPeJG5Hd8Nj1a/AmypbvyB041jjtpgKPg/jsnZWJdtxYjNyjsjD9R3BDiTAf58JD
+La73fudti7vZ++5LSn6LfSD60AyEE8l40d6QjhbQjgZ5NrSRzD6BvY5/WofyxwAD
+BxAAg2eSPzBxn5oioNoa5qgbtido/3XgvAqcHMgx9aL0NIB72/REmvglUVNlnctg
+g+0PUprTL3qweXp3i+NfIj1UXEHcEZkNsiB+LnZQWunHvR01AmnnAGeP7WAZ2gtA
+L//Zfbc9eHmHMETA6Qfm55IYBfj2dSgHw/cHGNmh27bH95WQXOYeKMDyZMaXvwTc
+7icVt6uFaUmy7GYcJwmdsIOw0cD+OMBSJNNKbZqAWOfYLFOiONF7c6xfCQ69HAOe
+8TQ+UWNtWfJZ52ERNU3PIeURKbPWSMVt/8pfdeNOGzy6sXM6VB0ADEkwJgWkrT2F
+9Z0udbqHzoS7OUijwlJsXxiiDobA9KC1J+/A55DjVRnl8wYm0sMLW4HzM3nuBvME
+lHye8PFl89U8mp2hi5yfg7igEkjqxaAQgxxA7lmVoEi++DTkLqRUZNIMTj5qlPeG
+PB84kFpmv3BE25g/N6vK8TfhjNqAcUJEBsXG6jExxO2lYVd7cpm0llWmIcMN6CML
+GUvaFCItQ5zxKMEugqVUa8Hby4yyZyc81TtwBT5/XmdUO0dbTcfs2eDjn4byPzpU
+Sdxwcdi+d3pm5G3WPYBWAgnU4z8oyqAyMZAC4Amo4wmDDVLACjUnepjAkPE7b7pE
+s5GUtURij9u6IbHZ8yFbIhAG8QzDHCOJNbpdCxMVEwl0o3gAAckBdfKuasiNUn5G
+L5XRnSvaOFzftr8zteOlZChCSNvzH5k+i1j7RJbWq06OeKRywPzjfjgM2MvRzI43
+ICeISQQYEQIACQUCQsgkdgIbDAAKCRBj+HX2P2d0oNfbAJ9+G3SeXrk+dWwo9EGi
+hqMi2GVTsgCfeoQJPsc8FLYUgfymc/3xqAVLUtg=
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..a5dd6d092c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,48 @@
+--
+-- PGP Public Key Encryption
+--
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select encode(pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey)), 'escape')
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..590d0a8559
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,601 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..2e3fc914e1 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,10 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal>, and
+ <literal>blowfish</literal> are only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1160,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1180,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v52-0006-nss-Documentation.patchapplication/octet-stream; name=v52-0006-nss-Documentation.patch; x-unix-mode=0644Download
From 90775bceb077e966090609f0ca2f446489903857 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v52 06/11] 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 | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4cd9818acf..630390bd7e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index a449719fd3..97aa9119d6 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 14f35d37f6..67dd923fce 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL server certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8716,6 +8889,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8742,6 +8921,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f77ed24204..132e8cbf80 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v52-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v52-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 144f9ae2c183fc82a10298e3542c6b0d9c0df9d9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v52 05/11] 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 be589c9d0b..2fec8e3bdf 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v52-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v52-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From c84b72a244e7d439a153c7e601ed9ad41d067a82 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v52 04/11] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
6 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 5d56455857..29110af614 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index d8995b1ae5..808ed8991b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 2b539d2402..0c0b7459fb 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index cdc4e1e5c8..39d8010aee 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 7af0f8db13..88f5e2746f 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2153,8 +2153,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7dc1731e64..430ce242d6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
--
2.24.3 (Apple Git-128)
v52-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v52-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From 0764549f37bb45894a5e01f942bf8684f85581c4 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v52 03/11] 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 | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 +++++++++++++++
src/test/ssl/t/001_ssltests.pl | 458 ++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/003_sslinfo.pl | 12 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 66 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 21 +-
src/test/ssl/t/SSL/Server.pm | 57 +---
9 files changed, 695 insertions(+), 235 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 12b02eb422..b22cb51556 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -18,14 +18,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..065e08a104 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 8d6b9dfbdc..7dc1731e64 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -361,66 +454,86 @@ $node->connect_fails(
# correct client cert in unencrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-der.key'}",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-der.key'),
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-der.key'} sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-der.key') . " sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": bad decrypt\E!
+ qr!connection requires a valid client certificate|private key file \".*/client-encrypted-pem\.key\": bad decrypt!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -431,49 +544,52 @@ TODO:
# correct client cert in encrypted PEM with empty password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword=''",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword=''",
"certificate authorization fails with correct client cert and empty password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
# correct client cert in encrypted PEM with no password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key'),
"certificate authorization fails with correct client cert and no password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
}
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno =~ s/\s+//g;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno =~ s/\s+//g;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -485,42 +601,47 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key') . " ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client_wrongperms.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client_wrongperms.key'),
"certificate authorization fails because of file permissions",
expected_stderr =>
- qr!\Qprivate key file "$key{'client_wrongperms.key'}" has group or world access\E!
+ qr!private key file \".*client_wrongperms\.key\" has group or world access!
);
}
# client cert belonging to another user
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -528,16 +649,16 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full succeeds with matching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
expected_stderr =>
qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
@@ -547,15 +668,15 @@ $node->connect_fails(
# Check that connecting with auth-optionverify-ca in pg_hba :
# works, when username doesn't match Common Name
$node->connect_ok(
- "$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=yetanotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=$key{'client.key'} sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb " . sslkey('client.key') . " sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -563,14 +684,15 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key') . " ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 56c5f88d5f..fd3c881040 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,24 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +43,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +61,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", 'password' => "pass", 'password_enc' => "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -104,7 +115,7 @@ chmod 0600, "$cert_tempdir/client_scram.key"
or die "failed to change permissions on $cert_tempdir/client_scram.key: $!";
$client_tmp_key =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -112,10 +123,10 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
]);
-done_testing($number_of_tests);
+done_testing();
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 8c760b39db..4dc457e1b6 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -12,15 +12,19 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan tests => 13;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'SSL not supported by this build';
}
else
{
- plan tests => 13;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
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..e2b9b8cfc9
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,66 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend get_nss_key);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub get_nss_key
+{
+ return " ";
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 96ed3475ca..7caa8f23ee 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -74,23 +74,26 @@ sub get_openssl_key
{
my $keyfile = shift;
- return $key{$keyfile};
+ return " sslkey=$key{$keyfile}";
}
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 4817db086e..ef7b17ed74 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -37,7 +37,7 @@ use File::Basename;
use File::Copy;
use Test::More;
use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
-use SSL::Backend::NSS qw(get_new_nss_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend get_nss_key);
our ($openssl, $nss, $backend);
@@ -58,9 +58,8 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
- key
+ sslkey
);
# Copy a set of files, taking into account wildcards
@@ -79,11 +78,12 @@ sub copy_files
return;
}
-sub key
+# Return a sslkey construct for use in a connection string.
+sub sslkey
{
my $keyfile = shift;
- return get_openssl_key($keyfile) if (defined($openssl);
+ return get_openssl_key($keyfile) if (defined($openssl));
return get_nss_key($keyfile) if (defined($nss));
}
@@ -168,7 +168,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -193,46 +193,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v52-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v52-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 2fb62f8b743fecd4e83f976c46c04abdf03c6962 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v52 02/11] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 57 +--------
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 109 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 92 ++++++++++++---
4 files changed, 194 insertions(+), 68 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (77%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1fb15ce80..8d6b9dfbdc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,39 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes to using keys stored in a temporary path for the rest of
-# the tests. To get the full path for inclusion in connection strings, the
-# %key hash can be interrogated.
-my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
-my %key;
-my @keys = (
- "client.key", "client-revoked.key",
- "client-der.key", "client-encrypted-pem.key",
- "client-encrypted-der.key", "client-dn.key");
-foreach my $keyfile (@keys)
-{
- copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
- or die
- "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
- chmod 0600, "$cert_tempdir/$keyfile"
- or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
- $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
- $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
- or die
- "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
-chmod 0644, "$cert_tempdir/client_wrongperms.key"
- or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
-$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
-$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
#### Set up the server.
note "setting up data directory";
@@ -83,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 86312be88c..56c5f88d5f 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..96ed3475ca
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,109 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+
+our (%key);
+
+INIT
+{
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes to using keys stored in a temporary path for the rest of
+ # the tests. To get the full path for inclusion in connection strings, the
+ # %key hash can be interrogated.
+ my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
+ my @keys = (
+ "client.key", "client-revoked.key",
+ "client-der.key", "client-encrypted-pem.key",
+ "client-encrypted-der.key", "client-dn.key");
+ foreach my $keyfile (@keys)
+ {
+ copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
+ or die
+ "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
+ chmod 0600, "$cert_tempdir/$keyfile"
+ or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
+ $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
+ $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
+ or die
+ "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
+ chmod 0644, "$cert_tempdir/client_wrongperms.key"
+ or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
+ $key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
+ $key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+}
+
+sub get_openssl_key
+{
+ my $keyfile = shift;
+
+ return $key{$keyfile};
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 77%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c85c6fd997..4817db086e 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,20 +26,41 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
+ key
);
# Copy a set of files, taking into account wildcards
@@ -58,6 +79,14 @@ sub copy_files
return;
}
+sub key
+{
+ my $keyfile = shift;
+
+ return get_openssl_key($keyfile) if (defined($openssl);
+ return get_nss_key($keyfile) if (defined($nss));
+}
+
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
sub configure_test_server_for_ssl
@@ -126,14 +155,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -141,20 +177,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -164,13 +216,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v52-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v52-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 297ee9ab31aa579e002edc335cce83dae19711b1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v52 01/11] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1586 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1200 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 29 +-
16 files changed, 3192 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7d6f7d9e3d..8582d9f98b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9509,7 +9509,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f3135..eda595624f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2754,7 +2754,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..2c0b20086e
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1586 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/nss.h"
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+#include <nss.h>
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secoidt.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * Since NSPR initialization must happen after forking, most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. This introduce
+ * differences with the OpenSSL support where some errors are only reported
+ * at runtime with NSS where they are reported at startup with OpenSSL.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ port->peer_cert_valid = false;
+ port->ssl_in_use = false;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if
+ * this is helpful or not. Using the policies works, but may be too
+ * coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return -1;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return -1;
+ }
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ /*
+ * NSS returns PR_END_OF_FILE_ERROR if the handshake was incomplete,
+ * which for example will happen if the client hasn't been set up with
+ * the correct certificates etc.
+ */
+ if (PR_GetError() == PR_END_OF_FILE_ERROR)
+ ereport(COMMERROR,
+ (errmsg("could not accept SSL connection: EOF detected")));
+ else
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register our shutdown callback */
+ NSS_RegisterShutdown(pg_SSLShutdownFunc, port);
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_SSLShutdownFunc
+ * Callback for NSS shutdown
+ *
+ * If NSS is terminated from the outside when the connection is still in use
+ * we must treat this as potentially hostile and immediately close to avoid
+ * leaking the connection in any way. Once this is called, NSS will shutdown
+ * regardless so we may as well clean up the best we can. Returning SECFailure
+ * will cause the NSS shutdown to return with an error, but it will shutdown
+ * nevertheless. nss_data is reserved for future use and is always NULL.
+ */
+static SECStatus
+pg_SSLShutdownFunc(void *private_data, void *nss_data)
+{
+ Port *port = (Port *) private_data;
+
+ if (!port || !port->ssl_in_use)
+ return SECSuccess;
+
+ /*
+ * There is a connection still open, close it and signal to whatever that
+ * called the shutdown that it was erroneous.
+ */
+ be_tls_close(port);
+ be_tls_destroy();
+
+ return SECFailure;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index a05f67afb5..1cd1a724d0 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c94f09c64..9f0e935820 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4452,7 +4452,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4520,6 +4524,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4548,8 +4562,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..30b32a7908
--- /dev/null
+++ b/src/common/cipher_nss.c
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_nss.c
+ * NSS functionality shared between frontend and backend for working
+ * with ciphers
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/common/cipher_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..4d12c9f388
--- /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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/nss.h b/src/include/common/nss.h
new file mode 100644
index 0000000000..10455ecbe5
--- /dev/null
+++ b/src/include/common/nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.h
+ * NSS supporting functionality shared between frontend and backend
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/common/nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_NSS_H
+#define COMMON_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index dd3e5efba3..d102bd56b5 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index f0786e08b4..e37b1b67eb 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d2e3e3a57..c2a0694669 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -202,7 +202,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index fe3af855d8..35442184ac 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..4592d1969d
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1200 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+
+#include <hasht.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule *ca_trust = NULL;
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ /*
+ * If the system trust module has been loaded we must try to unload it
+ * before closing the context, since it will otherwise fail reporting a
+ * SEC_ERROR_BUSY error.
+ */
+ if (ca_trust != NULL)
+ {
+ if (SECMOD_UnloadUserModule(ca_trust) != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to unload trust module");
+ }
+ else
+ {
+ SECMOD_DestroyModule(ca_trust);
+ ca_trust = NULL;
+ }
+ }
+
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_PK11RELOAD);
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initialize NSS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!certificate_database_has_CA(conn))
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate database does not contain a CA certificate\n"
+ "Either provide a database with a CA or change sslmode to disable server certificate verification.\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy by setting the domestic suite.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ Assert(conn->nss_context);
+ Assert(conn->pr_fd);
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ return 0;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname"));
+ return 0;
+ }
+
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ CERT_DestroyCertificate(server_cert);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 0b998e254d..b7dcf570af 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 20eb855abc..119947871b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fcce13843e..ea95f209cb 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/nss.h"
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +389,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +531,28 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -780,7 +807,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
On Tue, Jan 18, 2022 at 7:43 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 18 Jan 2022, at 07:36, Julien Rouhaud <rjuju123@gmail.com> wrote:
On Mon, Jan 17, 2022 at 03:09:11PM +0100, Daniel Gustafsson wrote:
I must've fat-fingered the "git add -p" for v50 as the fix was in configure.ac
but not configure. Fixed now.Thanks! Apparently this version now fails on all OS, e.g.:
Fixed, I had made a mistake in the OpenSSL.pm testcode and failed to catch it
in testing.
LGTM +1
On Wed, 2021-12-15 at 23:10 +0100, Daniel Gustafsson wrote:
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.
I'm currently tracking down a slot leak. When opening and closing large
numbers of NSS databases, at some point we appear to run out of slots
and then NSS starts misbehaving, even though we've closed all of our
context handles.
I don't have anything more helpful to share yet, but I wanted to make a
note of it here in case anyone else had seen it or has ideas on what
may be causing it. My next move will be to update the version of NSS
I'm running.
--Jacob
On 18 Jan 2022, at 17:37, Jacob Champion <pchampion@vmware.com> wrote:
On Wed, 2021-12-15 at 23:10 +0100, Daniel Gustafsson wrote:
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.I'm currently tracking down a slot leak. When opening and closing large
numbers of NSS databases, at some point we appear to run out of slots
and then NSS starts misbehaving, even though we've closed all of our
context handles.
Interesting, are you able to share a reproducer for this so I can assist in
debugging it?
--
Daniel Gustafsson https://vmware.com/
Hi,
On 2022-01-18 13:42:54 +0100, Daniel Gustafsson wrote:
Fixed, I had made a mistake in the OpenSSL.pm testcode and failed to catch it
in testing.
+task: + name: Linux - Debian Bullseye (nss) [ copy of a bunch of code ]
I also needed similar-but-not-quite-equivalent tasks for the meson patch as
well. I just moved to having a splitting the tasks into a template and a use
of it. It's probably not quite right as I did there, but it might be worth
looking into:
https://github.com/anarazel/postgres/blob/meson/.cirrus.yml#L181
But maybe this case actually has a better solution, see two paragraphs down:
+ install_script: | + DEBIAN_FRONTEND=noninteractive apt-get --yes install libnss3 libnss3-dev libnss3-tools libnspr4 libnspr4-dev
This needs an apt-get update beforehand to succeed. That's what caused the last few runs
to fail, see e.g.
https://cirrus-ci.com/task/6293612580306944
Just duplicating the task doesn't really scale once in tree. What about
reconfiguring (note: add --enable-depend) the linux tasks to build against
nss, and then run the relevant subset of tests with it? Most tests don't use
tcp / SSL anyway, so rerunning a small subset of tests should be feasible?
From 297ee9ab31aa579e002edc335cce83dae19711b1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v52 01/11] nss: Support libnss as TLS library in libpq
16 files changed, 3192 insertions(+), 7 deletions(-)
Phew. This is a huge patch.
Damn, I only opened this thread to report the CI failure. But now I ended up
doing a small review...
+#include "common/nss.h" + +/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * Remove backwards compatibility with ancient NSPR versions to avoid this. + */ +#define NO_NSPR_10_SUPPORT +#include <nspr.h> +#include <prerror.h> +#include <prio.h> +#include <prmem.h> +#include <prtypes.h>
Duplicated with nss.h. Which brings me to:
+#include <nss.h>
Is it a great idea to have common/nss.h when there's a library header nss.h?
Perhaps we should have a pg_ssl_{nss,openssl}.h or such?
+/* ------------------------------------------------------------ */ +/* Public interface */ +/* ------------------------------------------------------------ */
Nitpicks:
I don't think we typically do multiple /* */ comments in a row for this type
of thing. I also don't particularly like centering things like this, tends to
get inconsistent across comments.
+/* + * 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.
The "Since ... must happen after forking" sounds like it's referencing a
previously remarked upon fact. But I don't see anything but a copy of this
comment.
Does this make some things notably more expensive? Presumably it does remove a
bunch of COW opportunities, but likely that's not a huge factor compared to
assymetric crypto negotiation...
Maybe soem of this commentary should migrate to the file header or such?
This introduce + * differences with the OpenSSL support where some errors are only reported + * at runtime with NSS where they are reported at startup with OpenSSL.
Found this sentence hard to parse somehow.
It seems pretty unfriendly to only have minimal error checking at postmaster
startup time. Seems at least the presence and usability of keys should be done
*also* at that time?
+ /* + * If no ciphers are specified, enable them all. + */ + if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0) + { + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to set cipher policy: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + } + else + { + char *ciphers, + *c; + + char *sep = ":;, "; + PRUint16 ciphercode; + const PRUint16 *nss_ciphers; + bool found = false; + + /* + * If the user has specified a set of preferred cipher suites we start + * by turning off all the existing suites to avoid the risk of down- + * grades to a weaker cipher than expected. + */ + nss_ciphers = SSL_GetImplementedCiphers(); + for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++) + SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE); + + ciphers = pstrdup(SSLCipherSuites); + + for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep)) + { + if (pg_find_cipher(c, &ciphercode)) + { + status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE); + found = true; + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + return -1;
It likely doesn't matter much because the backend will exit, but because
COMERROR doesn't throw, it seems like this will leak "ciphers"?
+ } + } + } + + pfree(ciphers); + + if (!found) + { + ereport(COMMERROR, + (errmsg("no cipher-suites found"))); + return -1; + } + }
Seems like this could reasonably done in a separate function?
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port); + if (!server_cert) + { + if (dummy_ssl_passwd_cb_called) + { + ereport(COMMERROR, + (errmsg("unable to load certificate for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())), + errhint("The certificate requires a password."))); + return -1; + }
I assume PR_GetError() is some thread-local construct, given it's also used in
libpq? Why, oh why, do people copy the abysmal "global errno" approach
everywhere.
+ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{
I'm not a fan of duplicating the symbol names between be-secure-openssl.c and
this. For one it's annoying for source code naviation. It also seems that at
some point we might want to be able to link against both at the same time?
Maybe we should name them unambiguously and then use some indirection in a
header somewhere?
+ ssize_t n_read; + PRErrorCode err; + + n_read = PR_Read(port->pr_fd, ptr, len); + + if (n_read < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_READABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_read; +} + +ssize_t +be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_write; + PRErrorCode err; + PRIntn flags = 0; + + /* + * The flags parameter to PR_Send is no longer used and is, according to + * the documentation, required to be zero. + */ + n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT); + + if (n_write < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_WRITEABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_write; +} + +/* + * be_tls_close + * + * Callback for closing down the current connection, if any. + */ +void +be_tls_close(Port *port) +{ + if (!port) + return; + /* + * Immediately signal to the rest of the backend that this connnection is + * no longer to be considered to be using TLS encryption. + */ + port->ssl_in_use = false; + + if (port->peer_cn) + { + SSL_InvalidateSession(port->pr_fd); + pfree(port->peer_cn); + port->peer_cn = NULL; + } + + PR_Close(port->pr_fd); + port->pr_fd = NULL;
What if we failed before initializing pr_fd?
+ /* + * 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;
That's really not great. Can't we do something like initialize NSS in
postmaster, load the key into memory, including prompting, and then shut nss
down again?
+/* + * raw_subject_common_name + * + * Returns the Subject Common Name for the given certificate as a raw char + * buffer (that is, without any form of escaping for unprintable characters or + * embedded nulls), with the length of the buffer returned in the len param. + * The buffer is allocated in the TopMemoryContext and is given a NULL + * terminator so that callers are safe to call strlen() on it. + * + * This is used instead of CERT_GetCommonName(), which always performs quoting + * and/or escaping. NSS doesn't appear to give us a way to easily unescape the + * result, and we need to store the raw CN into port->peer_cn for compatibility + * with the OpenSSL implementation. + */
Do we have a testcase for embedded NULLs in common names?
+static char * +raw_subject_common_name(CERTCertificate *cert, unsigned int *len) +{ + CERTName subject = cert->subject; + CERTRDN **rdn; + + for (rdn = subject.rdns; *rdn; rdn++) + { + CERTAVA **ava; + + for (ava = (*rdn)->avas; *ava; ava++) + { + SECItem *buf; + char *cn; + + if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME) + continue; + + /* Found a CN, decode and copy it into a newly allocated buffer */ + buf = CERT_DecodeAVAValue(&(*ava)->value); + if (!buf) + { + /* + * This failure case is difficult to test. (Since this code + * runs after certificate authentication has otherwise + * succeeded, you'd need to convince a CA implementation to + * sign a corrupted certificate in order to get here.)
Why is that hard with a toy CA locally? Might not be worth the effort, but if
the comment explicitly talks about it being hard...
+ * Follow the behavior of CERT_GetCommonName() in this case and + * simply return NULL, as if a Common Name had not been found. + */ + goto fail; + } + + cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1); + memcpy(cn, buf->data, buf->len); + cn[buf->len] = '\0'; + + *len = buf->len; + + SECITEM_FreeItem(buf, PR_TRUE); + return cn; + } + } + +fail: + /* Not found */ + *len = 0; + return NULL; +}
+/* + * pg_SSLShutdownFunc + * Callback for NSS shutdown + * + * If NSS is terminated from the outside when the connection is still in use
What does "NSS is terminated from the outside when the connection" really
mean? Does this mean the client initiating something?
+ * we must treat this as potentially hostile and immediately close to avoid + * leaking the connection in any way. Once this is called, NSS will shutdown + * regardless so we may as well clean up the best we can. Returning SECFailure + * will cause the NSS shutdown to return with an error, but it will shutdown + * nevertheless. nss_data is reserved for future use and is always NULL. + */ +static SECStatus +pg_SSLShutdownFunc(void *private_data, void *nss_data) +{ + Port *port = (Port *) private_data; + + if (!port || !port->ssl_in_use) + return SECSuccess;
How can that happen?
+ /* + * 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();
And this doesn't have any dangerous around those functions getting called
again later?
+void +pgtls_close(PGconn *conn) +{ + conn->ssl_in_use = false; + conn->has_password = false; + + /* + * If the system trust module has been loaded we must try to unload it + * before closing the context, since it will otherwise fail reporting a + * SEC_ERROR_BUSY error. + */ + if (ca_trust != NULL) + { + if (SECMOD_UnloadUserModule(ca_trust) != SECSuccess) + { + pqInternalNotice(&conn->noticeHooks, + "unable to unload trust module"); + } + else + { + SECMOD_DestroyModule(ca_trust); + ca_trust = NULL; + } + }
Might just misunderstand: How can it be ok to destroy ca_trust here? What if
there's other connections using it? The same thread might be using multiple
connections, and multiple threads might be using connections. Seems very much
not thread safe.
+PostgresPollingStatusType +pgtls_open_client(PGconn *conn) +{ + SECStatus status; + PRFileDesc *model; + NSSInitParameters params; + SSLVersionRange desired_range; + +#ifdef ENABLE_THREAD_SAFETY +#ifdef WIN32 + /* This locking is modelled after fe-secure-openssl.c */ + if (ssl_config_mutex == NULL) + { + while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1) + /* loop while another thread owns the lock */ ; + if (ssl_config_mutex == NULL) + { + if (pthread_mutex_init(&ssl_config_mutex, NULL)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to lock thread")); + return PGRES_POLLING_FAILED; + } + } + InterlockedExchange(&win32_ssl_create_mutex, 0); + } +#endif + if (pthread_mutex_lock(&ssl_config_mutex)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to lock thread")); + return PGRES_POLLING_FAILED; + } +#endif /* ENABLE_THREAD_SAFETY */
I'd very much like to avoid duplicating this code. Can we put it somewhere
combined instead?
+ /* + * 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);
Why does this, and several subsequent bits, have to happen under a lock?
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0) + { + int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version); + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"), + conn->ssl_max_protocol_version); + return -1; + } + + desired_range.max = ssl_max_ver; + } + + if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to set allowed SSL protocol version range: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + }
Why are some parts returning -1 and some PGRES_POLLING_FAILED? -1 certainly
isn't a member of PostgresPollingStatusType.
+ /* + * 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. + */
Can we propose a patch to document them? Don't want to get bitten by this
suddenly changing...
From a12769bd793a8e073125c3b3a176b355335646bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v52 07/11] nss: Support NSS in pgcryptoThis 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.
I wish we didn't have pgcrypto in its current form.
From 5079ce8a677074b93ef1f118d535c6dee4ce64f9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v52 10/11] nss: Build infrastructureFinally this adds the infrastructure to build a postgres installation
with libnss support.
I would suggest trying to come up with a way to reorder / split the series so
that smaller pieces are committable. The way you have this right now leaves
you with applying all of it at once as the only realistic way. And this
patchset is too large for that.
Greetings,
Andres Freund
On Wed, 2022-01-19 at 10:01 +0100, Daniel Gustafsson wrote:
On 18 Jan 2022, at 17:37, Jacob Champion <pchampion@vmware.com> wrote:
On Wed, 2021-12-15 at 23:10 +0100, Daniel Gustafsson wrote:
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.I'm currently tracking down a slot leak. When opening and closing large
numbers of NSS databases, at some point we appear to run out of slots
and then NSS starts misbehaving, even though we've closed all of our
context handles.Interesting, are you able to share a reproducer for this so I can assist in
debugging it?
(This was in my spam folder, sorry for the delay...) Let me see if I
can minimize my current reproduction case and get it ported out of
Python.
--Jacob
On Tue, 2022-01-25 at 22:26 +0000, Jacob Champion wrote:
On Wed, 2022-01-19 at 10:01 +0100, Daniel Gustafsson wrote:
On 18 Jan 2022, at 17:37, Jacob Champion <pchampion@vmware.com> wrote:
On Wed, 2021-12-15 at 23:10 +0100, Daniel Gustafsson wrote:
I've attached a v50 which fixes the issues found by Joshua upthread, as well as
rebases on top of all the recent SSL and pgcrypto changes.I'm currently tracking down a slot leak. When opening and closing large
numbers of NSS databases, at some point we appear to run out of slots
and then NSS starts misbehaving, even though we've closed all of our
context handles.Interesting, are you able to share a reproducer for this so I can assist in
debugging it?(This was in my spam folder, sorry for the delay...) Let me see if I
can minimize my current reproduction case and get it ported out of
Python.
Here's my attempt at a Bash port. It has races but reliably reproduces
on my machine after 98 connections (there's a hardcoded slot limit of
100, so that makes sense when factoring in the internal NSS slots).
--Jacob
Attachments:
On 23 Jan 2022, at 22:20, Andres Freund <andres@anarazel.de> wrote:
On 2022-01-18 13:42:54 +0100, Daniel Gustafsson wrote:
Thanks heaps for the review, much appreciated!
+ install_script: | + DEBIAN_FRONTEND=noninteractive apt-get --yes install libnss3 libnss3-dev libnss3-tools libnspr4 libnspr4-devThis needs an apt-get update beforehand to succeed. That's what caused the last few runs
to fail, see e.g.
https://cirrus-ci.com/task/6293612580306944
Ah, good point. Adding that made it indeed work.
Just duplicating the task doesn't really scale once in tree.
Totally agree. This was mostly a hack to see if I could make the CFBot build a
tailored build, then life threw school closures etc at me and I sort of forgot
about removing it again.
What about
reconfiguring (note: add --enable-depend) the linux tasks to build against
nss, and then run the relevant subset of tests with it? Most tests don't use
tcp / SSL anyway, so rerunning a small subset of tests should be feasible?
That's an interesting idea, I think that could work and be reasonably readable
at the same time (and won't require in-depth knowledge of Cirrus). As it's the
same task it does spend more time towards the max runtime per task, but that's
not a problem for now. It's worth keeping in mind though if we deem this to be
a way forward with testing multiple settings.
From 297ee9ab31aa579e002edc335cce83dae19711b1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v52 01/11] nss: Support libnss as TLS library in libpq16 files changed, 3192 insertions(+), 7 deletions(-)
Phew. This is a huge patch.
Yeah =/ .. without going beyond and inventing new things on top what is needed
to replace OpenSSL, a lot of code (and tests) has to be written. If nothing
else, this work at least highlights just how much we've come to use OpenSSL.
Damn, I only opened this thread to report the CI failure. But now I ended up
doing a small review...
Thanks! Next time we meet, I owe you a beverage of choice.
+#include "common/nss.h" + +/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * Remove backwards compatibility with ancient NSPR versions to avoid this. + */ +#define NO_NSPR_10_SUPPORT +#include <nspr.h> +#include <prerror.h> +#include <prio.h> +#include <prmem.h> +#include <prtypes.h>Duplicated with nss.h. Which brings me to:
Fixed, there and elsewhere.
+#include <nss.h>
Is it a great idea to have common/nss.h when there's a library header nss.h?
Perhaps we should have a pg_ssl_{nss,openssl}.h or such?
That's a good point, I modelled it after common/openssl.h but I agree it's
better to differentiate the filenames. I've renamed it to common/pg_nss.h and
we should IMO rename common/openssl.h regardless of what happens to this patch.
+/* ------------------------------------------------------------ */ +/* Public interface */ +/* ------------------------------------------------------------ */Nitpicks:
I don't think we typically do multiple /* */ comments in a row for this type
of thing. I also don't particularly like centering things like this, tends to
get inconsistent across comments.
This is just a copy/paste from be-secure-openssl.c, but I'm far from married to
it so happy to remove. Fixed.
+/* + * 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.The "Since ... must happen after forking" sounds like it's referencing a
previously remarked upon fact. But I don't see anything but a copy of this
comment.
NSS contexts aren't fork safe, IIRC it's around its use of file descriptors.
Fairly old NSS documentation and mailing list posts cite hardware tokens (which
was a very strong focus in the earlier days of NSS) not being safe to use across
forks and thus none of NSS was ever intended to be initialized until after the
fork. I've reworded this comment a bit to make that clearer.
Does this make some things notably more expensive? Presumably it does remove a
bunch of COW opportunities, but likely that's not a huge factor compared to
assymetric crypto negotiation...
Right, the context of setting up crypto across a network connection it's highly
likely to drown out the costs.
Maybe soem of this commentary should migrate to the file header or such?
Maybe, or perhaps README.ssl? Not sure where it would be most reasonable to
keep it such that it's also kept up to date.
This introduce + * differences with the OpenSSL support where some errors are only reported + * at runtime with NSS where they are reported at startup with OpenSSL.Found this sentence hard to parse somehow.
It seems pretty unfriendly to only have minimal error checking at postmaster
startup time. Seems at least the presence and usability of keys should be done
*also* at that time?
I'll look at adding some setup, and subsequent teardown, of NSS at startup
during which we could do checking to be more on par with how the OpenSSL
backend will report errors.
+ /* + * If no ciphers are specified, enable them all. + */ + if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0) + { + ... + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + return -1;It likely doesn't matter much because the backend will exit, but because
COMERROR doesn't throw, it seems like this will leak "ciphers"?
Agreed, it won't matter much in practice but we should clearly pfree it, fixed.
+ pfree(ciphers); + + if (!found) + { + ereport(COMMERROR, + (errmsg("no cipher-suites found"))); + return -1; + } + }Seems like this could reasonably done in a separate function?
Agreed, trimming the length of an already very long function is a good idea.
Fixed.
I assume PR_GetError() is some thread-local construct, given it's also used in
libpq?
Correct.
Why, oh why, do people copy the abysmal "global errno" approach everywhere.
Even better, NSPR has two of them: PR_GetError and PR_GetOSError (the latter
isn't used in this implementation, but it could potentially be added to error
paths on NSS_InitContext and other calls that read off the filesystem).
+ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{I'm not a fan of duplicating the symbol names between be-secure-openssl.c and
this. For one it's annoying for source code naviation. It also seems that at
some point we might want to be able to link against both at the same time?
Maybe we should name them unambiguously and then use some indirection in a
header somewhere?
We could do that, and that's something that we could do independently of this
patch to keep the scope down. Doing it in master now with just the OpenSSL
implementation as a consumer would be a logical next step in the TLS library
abstraction we've done.
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;What if we failed before initializing pr_fd?
Fixed.
+ /* + * 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;That's really not great. Can't we do something like initialize NSS in
postmaster, load the key into memory, including prompting, and then shut nss
down again?
I can look at doing something along those lines. It does require setting up a
fair bit of infrastructure but if the code refactored to allow reuse it can
probably be done fairly readable.
+/* + * raw_subject_common_name + * + * Returns the Subject Common Name for the given certificate as a raw char + * buffer (that is, without any form of escaping for unprintable characters or + * embedded nulls), with the length of the buffer returned in the len param. + * The buffer is allocated in the TopMemoryContext and is given a NULL + * terminator so that callers are safe to call strlen() on it. + * + * This is used instead of CERT_GetCommonName(), which always performs quoting + * and/or escaping. NSS doesn't appear to give us a way to easily unescape the + * result, and we need to store the raw CN into port->peer_cn for compatibility + * with the OpenSSL implementation. + */Do we have a testcase for embedded NULLs in common names?
We don't, neither for OpenSSL or NSS. AFAICR Jacob spent days trying to get a
certificate generation to include an embedded NULL byte but in the end gave up.
We would have to write our own tools for generating certificates to add that
(which may or may not be a bad idea, but it hasn't been done).
+ /* Found a CN, decode and copy it into a newly allocated buffer */ + buf = CERT_DecodeAVAValue(&(*ava)->value); + if (!buf) + { + /* + * This failure case is difficult to test. (Since this code + * runs after certificate authentication has otherwise + * succeeded, you'd need to convince a CA implementation to + * sign a corrupted certificate in order to get here.)Why is that hard with a toy CA locally? Might not be worth the effort, but if
the comment explicitly talks about it being hard...
The gist of this comment is that it's hard to do with a stock local CA. I've
added a small blurb to clarify that a custom implementation would be required.
+/* + * pg_SSLShutdownFunc + * Callback for NSS shutdown + * + * If NSS is terminated from the outside when the connection is still in useWhat does "NSS is terminated from the outside when the connection" really
mean? Does this mean the client initiating something?
If an extension, or other server-loaded code, interfered with NSS and managed
to close contexts in order to interfere with connections this would ensure us
closing it down cleanly.
That being said, I was now unable to get my old testcase working so I've for
now removed this callback from the patch until I can work out if we can make
proper use of it. AFAICS other mature NSS implementations aren't using it
(OpenLDAP did in the past but have since removed it, will look at how/why).
+ else + { + SECMOD_DestroyModule(ca_trust); + ca_trust = NULL; + } + }Might just misunderstand: How can it be ok to destroy ca_trust here? What if
there's other connections using it? The same thread might be using multiple
connections, and multiple threads might be using connections. Seems very much
not thread safe.
Right, that's a leftover from early hacking that I had missed. Fixed.
+ /* This locking is modelled after fe-secure-openssl.c */ + if (ssl_config_mutex == NULL) + { + ...I'd very much like to avoid duplicating this code. Can we put it somewhere
combined instead?
I can look at splitting it out to fe-secure-common.c. A first step here to
keep the goalposts from moving in this patch would be to look at combining lock
init in fe-secure-openssl.c:pgtls_init() and fe-connect.c:default_threadlock,
and then just apply the same recipe here once landed. This could be done
independent of this patch.
+ /* + * 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);Why does this, and several subsequent bits, have to happen under a lock?
NSS initialization isn't thread-safe, there is more discussion upthread in and
around this email:
/messages/by-id/c8d4bc0dfd266799ab4213f1673a813786ac0c70.camel@vmware.com
Why are some parts returning -1 and some PGRES_POLLING_FAILED? -1 certainly
isn't a member of PostgresPollingStatusType.
That was a thinko, fixed.
+ /* + * 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. + */Can we propose a patch to document them? Don't want to get bitten by this
suddenly changing...
I can certainly propose something on their mailinglist, but I unfortunately
wouldn't get my hopes up too high as NSS and documentation aren't exactly best
friends (the in-tree docs doesn't cover the API and Mozilla recently removed
most of the online docs in their neverending developer site reorg).
From a12769bd793a8e073125c3b3a176b355335646bc Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v52 07/11] nss: Support NSS in pgcryptoThis 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.I wish we didn't have pgcrypto in its current form.
Yes. Very much yes. I don't think doing anything about that in the context of
this patch is wise, but a discussion on where to take pgcrypto in the future
would probably be a good idea.
From 5079ce8a677074b93ef1f118d535c6dee4ce64f9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v52 10/11] nss: Build infrastructureFinally this adds the infrastructure to build a postgres installation
with libnss support.I would suggest trying to come up with a way to reorder / split the series so
that smaller pieces are committable. The way you have this right now leaves
you with applying all of it at once as the only realistic way. And this
patchset is too large for that.
I completely agree, the hard part is identifying smaller sets which also make
sense and which doesn't leave the tree in a bad state should anyone check out
that specific point in time.
The two commits in the patchset that are "easy" to consider for pushing
independently in this regard are IMO:
* 0002 Test refactoring to support multiple TLS libraries.
* 0004 Check for empty stderr during connect_ok
The refactoring in 0002 is hopefully not too controversial, but it clearly
needs eyes from someone more familiar with modern and idiomatic Perl. 0004
could IMO be pushed regardless of the fate of this patchset (after being
floated in its own thread on -hackers).
In order to find a good split I think we need to figure what to optimize for;
do we optimize for ease of reverting should that be needed, or along
functionality borders, or something else? I don't have good ideas here, but a
single 7596 insertions(+), 421 deletions(-) commit is clearly not a good idea.
Stephen had an idea off-list that we could look at splitting this across the
server/client boundary, which I think is the only idea I've so far which has
legs. (The first to go in would come with the common code of course.)
Do you have any thoughts after reading through the patch?
The attached v53 incorporates the fixes discussed above, and builds green for
both OpenSSL and NSS in Cirrus on my Github repo (thanks again for your work on
those files) so it will be interesting to see the CFBot running them. Next
would be to figure out how to make the MSVC build it, basing an attempt on
Andrew's blogpost.
--
Daniel Gustafsson https://vmware.com/
Attachments:
v53-0005-nss-pg_strong_random-support.patchapplication/octet-stream; name=v53-0005-nss-pg_strong_random-support.patch; x-unix-mode=0644Download
From 9560c9fd52236ba9f2b5f1f52f0d07a8bc03ab08 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:39 +0100
Subject: [PATCH v53 05/11] 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 be589c9d0b..2fec8e3bdf 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -137,10 +137,58 @@ pg_strong_random(void *buf, size_t len)
return false;
}
-#else /* not USE_OPENSSL or WIN32 */
+#elif defined(USE_NSS)
+
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+#define NO_NSPR_10_SUPPORT
+#include <nss.h>
+#include <pk11pub.h>
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+void
+pg_strong_random_init(void)
+{
+ /* No initialization needed on NSS */
+}
+
+bool
+pg_strong_random(void *buf, size_t len)
+{
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ SECStatus status;
+
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = PK11_GenerateRandom(buf, len);
+ NSS_ShutdownContext(nss_context);
+
+ if (status == SECSuccess)
+ return true;
+
+ return false;
+}
+
+#else /* not USE_OPENSSL, USE_NSS or WIN32 */
/*
- * Without OpenSSL or Win32 support, just read /dev/urandom ourselves.
+ * Without OpenSSL, NSS or Win32 support, just read /dev/urandom ourselves.
*/
void
--
2.24.3 (Apple Git-128)
v53-0011-NSS-experimental-support-for-NSS-in-CI.patchapplication/octet-stream; name=v53-0011-NSS-experimental-support-for-NSS-in-CI.patch; x-unix-mode=0644Download
From 5ce13b70d9d5d2300fb61189386afbf90e550b21 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sat, 18 Dec 2021 16:06:56 +0100
Subject: [PATCH v53 11/11] NSS: experimental support for NSS in CI
---
.cirrus.yml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index 677bdf0e65..aa17929421 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,6 @@ LINUX_CONFIGURE_FEATURES: &LINUX_CONFIGURE_FEATURES >-
--with-perl
--with-python
--with-selinux
- --with-ssl=openssl
--with-systemd
--with-tcl --with-tclconfig=/usr/lib/tcl8.6/
--with-uuid=ossp
@@ -194,6 +193,81 @@ task:
./configure \
--enable-cassert --enable-debug --enable-tap-tests \
--enable-nls \
+ --with-ssl=openssl \
+ \
+ ${LINUX_CONFIGURE_FEATURES} \
+ \
+ CC="ccache gcc" \
+ CXX="ccache g++" \
+ CLANG="ccache clang" \
+ CFLAGS="-O0 -ggdb" \
+ CXXFLAGS="-O0 -ggdb"
+ EOF
+ build_script: su postgres -c "make -s -j${BUILD_JOBS} world-bin"
+ upload_caches: ccache
+
+ test_world_script: |
+ su postgres <<-EOF
+ ulimit -c unlimited # default is 0
+ make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
+ EOF
+
+ on_failure:
+ <<: *on_failure
+ cores_script: src/tools/ci/cores_backtrace.sh linux /tmp/cores
+
+
+task:
+ name: Linux - Debian Bullseye (nss)
+
+ env:
+ CPUS: 4
+ BUILD_JOBS: 4
+ TEST_JOBS: 8 # experimentally derived to be a decent choice
+
+ CCACHE_DIR: /tmp/ccache_dir
+ DEBUGINFOD_URLS: "https://debuginfod.debian.net"
+
+ LINUX_CONFIGURE_FEATURES: *LINUX_CONFIGURE_FEATURES
+
+ only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*linux.*'
+
+ compute_engine_instance:
+ image_project: $IMAGE_PROJECT
+ image: family/pg-ci-bullseye
+ platform: linux
+ cpu: $CPUS
+ memory: 2G
+
+ ccache_cache:
+ folder: ${CCACHE_DIR}
+
+ sysinfo_script: |
+ id
+ uname -a
+ cat /proc/cmdline
+ ulimit -a -H && ulimit -a -S
+ export
+ create_user_script: |
+ useradd -m postgres
+ chown -R postgres:postgres .
+ mkdir -p ${CCACHE_DIR}
+ chown -R postgres:postgres ${CCACHE_DIR}
+ echo '* - memlock 134217728' > /etc/security/limits.d/postgres.conf
+ su postgres -c "ulimit -l -H && ulimit -l -S"
+ setup_cores_script: |
+ mkdir -m 770 /tmp/cores
+ chown root:postgres /tmp/cores
+ sysctl kernel.core_pattern='/tmp/cores/%e-%s-%p.core'
+ install_script: |
+ DEBIAN_FRONTEND=noninteractive apt-get update && apt-get --yes install libnss3 libnss3-dev libnss3-tools libnspr4 libnspr4-dev
+
+ configure_script: |
+ su postgres <<-EOF
+ ./configure \
+ --enable-cassert --enable-debug --enable-tap-tests \
+ --enable-nls \
+ --with-ssl=nss \
\
${LINUX_CONFIGURE_FEATURES} \
\
@@ -209,6 +283,7 @@ task:
test_world_script: |
su postgres <<-EOF
ulimit -c unlimited # default is 0
+ make -C src/test/ssl/ nssfiles
make -s ${CHECK} ${CHECKFLAGS} -j${TEST_JOBS}
EOF
--
2.24.3 (Apple Git-128)
v53-0010-nss-Build-infrastructure.patchapplication/octet-stream; name=v53-0010-nss-Build-infrastructure.patch; x-unix-mode=0644Download
From aa96135afb12cc9fdebdc13a5ad5e7d2cbc74b91 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:55 +0100
Subject: [PATCH v53 10/11] nss: Build infrastructure
Finally this adds the infrastructure to build a postgres installation
with libnss support.
---
configure | 376 +++++++++++++++++++++++++++++++++-
configure.ac | 81 +++++++-
contrib/Makefile | 2 +-
src/backend/libpq/Makefile | 4 +
src/common/Makefile | 8 +
src/include/pg_config.h.in | 12 ++
src/interfaces/libpq/Makefile | 5 +
src/tools/msvc/Install.pm | 6 +-
src/tools/msvc/Mkvcbuild.pm | 30 ++-
src/tools/msvc/Solution.pm | 26 +++
10 files changed, 536 insertions(+), 14 deletions(-)
diff --git a/configure b/configure
index 93a46a859b..21f9671d75 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,8 @@ UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -1577,7 +1579,7 @@ Optional Packages:
--without-zlib do not use Zlib
--with-lz4 build with LZ4 support
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
- --with-ssl=LIB use LIB for SSL/TLS support (openssl)
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
--with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
@@ -12924,8 +12926,356 @@ done
$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+elif test "$with_ssl" = nss ; then
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 3 && ($2 >= 42)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSS version $NSS_VERSION" >&5
+$as_echo "$as_me: using NSS version $NSS_VERSION" >&6;}
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define RC_INVOKED
+#include <nss/nss.h>
+
+int
+main ()
+{
+
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed 's/[.]/ /g' | \
+ $AWK '{if ($1 == 4 && ($2 >= 20)) exit 1; else exit 0; }'
+ then
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION." "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: using NSPR version $NSPR_VERSION" >&5
+$as_echo "$as_me: using NSPR version $NSPR_VERSION" >&6;}
+ else
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+
+int
+main ()
+{
+
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ as_fn_error $? "
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required." "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
elif test "$with_ssl" != no ; then
- as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
fi
@@ -13896,6 +14246,23 @@ else
fi
+elif test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18663,6 +19030,9 @@ $as_echo_n "checking which random number source to use... " >&6; }
if test x"$with_ssl" = x"openssl" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+elif test x"$with_ssl" = x"nss" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5
+$as_echo "NSS" >&6; }
elif test x"$PORTNAME" = x"win32" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5
$as_echo "Windows native" >&6; }
@@ -18695,7 +19065,7 @@ fi
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
as_fn_error $? "
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5
fi
fi
diff --git a/configure.ac b/configure.ac
index 95287705f6..b4bb1b14db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1270,7 +1270,7 @@ fi
#
# There is currently only one supported SSL/TLS library: OpenSSL.
#
-PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)])
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
if test x"$with_ssl" = x"" ; then
with_ssl=no
fi
@@ -1304,8 +1304,78 @@ if test "$with_ssl" = openssl ; then
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
AC_DEFINE([USE_OPENSSL], 1, [Define to 1 to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = nss ; then
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ NSS_VERSION=`$NSS_CONFIG --version`
+ if echo "$NSS_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$]1 == 3 && ([$]2 >= 42)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required, this is $NSS_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSS version $NSS_VERSION])
+ else
+ # No nss-config was found, manually try to get the version and check for
+ # library capabilities
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define RC_INVOKED
+#include <nss/nss.h>
+], [
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 42)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSS is too old to use with PostgreSQL.
+*** NSS version 3.42 or later is required.])])
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ fi
+
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ NSPR_VERSION=`$NSPR_CONFIG --version`
+
+ if echo "$NSPR_VERSION" | sed ['s/[.]/ /g'] | \
+ $AWK '{if ([$1] == 4 && ([$]2 >= 20)) exit 1; else exit 0; }'
+ then
+ AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required, this is $NSPR_VERSION.])
+ fi
+ AC_MSG_NOTICE([using NSPR version $NSPR_VERSION])
+ else
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+#define NO_NSPR_10_SUPPORT
+#include <nspr/prinit.h>
+], [
+#if PR_VMAJOR < 4 || (PR_VMAJOR == 4 && PR_VMINOR < 20)
+choke me
+#endif
+])], [],
+[AC_MSG_ERROR([
+*** The installed version of NSPR is too old to use with PostgreSQL.
+*** NSPR version 4.20 or later is required.])])
+
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+ CPPFLAGS="$CPPFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
elif test "$with_ssl" != no ; then
- AC_MSG_ERROR([--with-ssl must specify openssl])
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
AC_SUBST(with_ssl)
@@ -1496,6 +1566,9 @@ fi
if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
+elif test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
fi
if test "$with_pam" = yes ; then
@@ -2293,6 +2366,8 @@ fi
AC_MSG_CHECKING([which random number source to use])
if test x"$with_ssl" = x"openssl" ; then
AC_MSG_RESULT([OpenSSL])
+elif test x"$with_ssl" = x"nss" ; then
+ AC_MSG_RESULT([NSS])
elif test x"$PORTNAME" = x"win32" ; then
AC_MSG_RESULT([Windows native])
elif test x"$cross_compiling" = x"yes"; then
@@ -2304,7 +2379,7 @@ else
if test x"$ac_cv_file__dev_urandom" = x"no" ; then
AC_MSG_ERROR([
no source of strong random numbers was found
-PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.])
+PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.])
fi
fi
diff --git a/contrib/Makefile b/contrib/Makefile
index 87bf87ab90..4d2bba31ea 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_ssl),openssl)
+ifneq (,$(filter $(with_ssl),openssl nss))
SUBDIRS += pgcrypto sslinfo
else
ALWAYS_SUBDIRS += pgcrypto sslinfo
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a4..344fdf2e58 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -31,6 +31,10 @@ OBJS = \
ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/common/Makefile b/src/common/Makefile
index 31c0dd366d..501009ce2a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -86,6 +86,13 @@ OBJS_COMMON += \
cryptohash_openssl.o \
hmac_openssl.o
else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+ hmac.o \
+ cipher_nss.o \
+ protocol_nss.o \
+ cryptohash_nss.o
+else
OBJS_COMMON += \
cryptohash.o \
hmac.o \
@@ -93,6 +100,7 @@ OBJS_COMMON += \
sha1.o \
sha2.o
endif
+endif
# A few files are currently only built for frontend, not server
# (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9d9bd6b9ef..25a31c0eee 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -328,6 +328,12 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
+/* Define to 1 if you have the `nspr4' library (-lnspr4). */
+#undef HAVE_LIBNSPR4
+
+/* Define to 1 if you have the `nss3' library (-lnss3). */
+#undef HAVE_LIBNSS3
+
/* Define to 1 if you have the `pam' library (-lpam). */
#undef HAVE_LIBPAM
@@ -340,6 +346,9 @@
/* Define to 1 if you have the `ssl' library (-lssl). */
#undef HAVE_LIBSSL
+/* Define to 1 if you have the `ssl3' library (-lssl3). */
+#undef HAVE_LIBSSL3
+
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
@@ -917,6 +926,9 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
+/* Define to 1 if you have NSS support, */
+#undef USE_NSS
+
/* Define to 1 to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 844c95d47d..fdf32dbbca 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -56,6 +56,11 @@ OBJS += \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 8de79c618c..facc454e3d 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -440,8 +440,10 @@ 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 "pgcrypto" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
+ next if ($d eq "pgcrypto" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index ec3546d0c0..0bf67ff6df 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -138,6 +138,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');
@@ -203,12 +209,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -266,12 +279,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -443,7 +463,7 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
- if (!$solution->{options}->{openssl})
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback', 'pgcrypto';
}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index e47c2d648c..8c45e599ec 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -303,10 +303,13 @@ sub GenerateFiles
HAVE_LIBLDAP => undef,
HAVE_LIBLZ4 => undef,
HAVE_LIBM => undef,
+ HAVE_LIBNSPR4 => undef,
+ HAVE_LIBNSS3 => undef,
HAVE_LIBPAM => undef,
HAVE_LIBREADLINE => undef,
HAVE_LIBSELINUX => undef,
HAVE_LIBSSL => undef,
+ HAVE_LIBSSL3 => undef,
HAVE_LIBWLDAP32 => undef,
HAVE_LIBXML2 => undef,
HAVE_LIBXSLT => undef,
@@ -496,6 +499,7 @@ sub GenerateFiles
USE_LLVM => undef,
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -557,6 +561,13 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ $define{HAVE_LIBNSPR4} = 1;
+ $define{HAVE_LIBNSS3} = 1;
+ $define{HAVE_LIBSSL3} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1016,6 +1027,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
--
2.24.3 (Apple Git-128)
v53-0009-nss-Support-NSS-in-cryptohash.patchapplication/octet-stream; name=v53-0009-nss-Support-NSS-in-cryptohash.patch; x-unix-mode=0644Download
From 98facb45e8a7f4cfcf7c507a8f77536ebb08664a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:51 +0100
Subject: [PATCH v53 09/11] 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 | 306 ++++++++++++++++++++++++++++++++++++
1 file changed, 306 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..66c317506d
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,306 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/pg_nss.h"
+
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * In the backend, use an allocation in TopMemoryContext to count for
+ * resowner cleanup handling. In the frontend, use malloc to be able
+ * to return a failure status back to the caller.
+ */
+#ifndef FRONTEND
+#define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
+#define FREE(ptr) pfree(ptr)
+#else
+#define ALLOC(size) malloc(size)
+#define FREE(ptr) free(ptr)
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+ pg_cryptohash_type type;
+
+ PK11Context *pk11_context;
+ SECOidTag hash_type;
+ const char *errreason;
+
+#ifndef FRONTEND
+ ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+ NSSInitParameters params;
+ pg_cryptohash_ctx *ctx;
+ SECOidData *hash;
+ SECStatus status;
+
+#ifndef FRONTEND
+ /*
+ * Make sure that the resource owner has space to remember this reference.
+ * This can error out with "out of memory", so do this before any other
+ * allocation to avoid leaking.
+ */
+ ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+#endif
+
+ ctx = ALLOC(sizeof(pg_cryptohash_ctx));
+
+ if (!ctx)
+ goto error;
+
+ ctx->errreason = NULL;
+ ctx->type = type;
+
+ switch(type)
+ {
+ case PG_SHA1:
+ ctx->hash_type = SEC_OID_SHA1;
+ break;
+ case PG_MD5:
+ ctx->hash_type = SEC_OID_MD5;
+ break;
+ case PG_SHA224:
+ ctx->hash_type = SEC_OID_SHA224;
+ break;
+ case PG_SHA256:
+ ctx->hash_type = SEC_OID_SHA256;
+ break;
+ case PG_SHA384:
+ ctx->hash_type = SEC_OID_SHA384;
+ break;
+ case PG_SHA512:
+ ctx->hash_type = SEC_OID_SHA512;
+ break;
+ }
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ status = NSS_NoDB_Init(".");
+ if (status != SECSuccess)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+ hash = SECOID_FindOIDByTag(ctx->hash_type);
+ ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+ if (!ctx->pk11_context)
+ {
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ goto error;
+ }
+
+#ifndef FRONTEND
+ ctx->resowner = CurrentResourceOwner;
+ ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+ PointerGetDatum(ctx));
+#endif
+
+ return ctx;
+
+error:
+#ifndef FRONTEND
+ if (ctx->errreason != NULL)
+ ereport(ERROR,
+ (errmsg("NSS failure: %s", ctx->errreason)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+#endif
+ return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ * Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+ SECStatus status;
+
+ status = PK11_DigestBegin(ctx->pk11_context);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+ SECStatus status;
+
+ if (ctx == NULL)
+ return -1;
+
+ status = PK11_DigestOp(ctx->pk11_context, data, len);
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context. Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
+{
+ SECStatus status;
+ unsigned int outlen;
+
+ switch (ctx->type)
+ {
+ case PG_MD5:
+ if (len < MD5_LENGTH)
+ return -1;
+ break;
+ case PG_SHA1:
+ if (len < SHA1_LENGTH)
+ return -1;
+ break;
+ case PG_SHA224:
+ if (len < SHA224_LENGTH)
+ return -1;
+ break;
+ case PG_SHA256:
+ if (len < SHA256_LENGTH)
+ return -1;
+ break;
+ case PG_SHA384:
+ if (len < SHA384_LENGTH)
+ return -1;
+ break;
+ case PG_SHA512:
+ if (len < SHA512_LENGTH)
+ return -1;
+ break;
+ }
+
+ status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, len);
+
+ if (status != SECSuccess)
+ {
+ ctx->errreason = PR_ErrorToString(PR_GetError(),
+ PR_LANGUAGE_I_DEFAULT);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+ PRBool free_context = PR_TRUE;
+
+ if (!ctx)
+ return;
+
+ PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+ ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+ explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+ FREE(ctx);
+}
+
+/*
+ * pg_cryptohash_error
+ *
+ * Returns a static string providing details about an error that happened
+ * during a computation.
+ */
+const char *
+pg_cryptohash_error(pg_cryptohash_ctx *ctx)
+{
+ if (!ctx)
+ return _("out of memory");
+
+ if (ctx->errreason)
+ return ctx->errreason;
+
+ /*
+ * PG_ErrorToString never returns NULL, and all error codepaths invoke it
+ * so if we end up here we either don't have any error at all or something
+ * very bad happened.
+ */
+ return _("unknown failure");
+}
--
2.24.3 (Apple Git-128)
v53-0008-nss-Support-NSS-in-sslinfo.patchapplication/octet-stream; name=v53-0008-nss-Support-NSS-in-sslinfo.patch; x-unix-mode=0644Download
From 2aa2f7ff9d5c9dcb6f3b2986988f531514e363f9 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:48 +0100
Subject: [PATCH v53 08/11] nss: Support NSS in sslinfo
Since sslinfo to a large extent uses the be_tls_* API this mostly
disables functionality which currently is OpenSSL specific.
---
contrib/sslinfo/sslinfo.c | 32 +++++++++++++++
doc/src/sgml/sslinfo.sgml | 12 +++++-
src/test/ssl/sslfiles_nss.mk | 12 +++++-
src/test/ssl/t/003_sslinfo.pl | 76 +++++++++++++++++------------------
4 files changed, 92 insertions(+), 40 deletions(-)
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5fd46b9874..25a068fa60 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -32,6 +34,7 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
@@ -42,6 +45,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -142,6 +146,7 @@ ssl_client_serial(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -293,7 +298,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
/*
* Returns current client certificate subject as one string
@@ -349,6 +370,7 @@ ssl_issuer_dn(PG_FUNCTION_ARGS)
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -482,3 +504,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index 2a9c45a111..f3ae2fc3b8 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-ssl=openssl</literal>.
+ configured with SSL support, such as <literal>--with-ssl=openssl</literal>
+ or <literal>--with-ssl=nss</literal>.
</para>
<sect2>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate issuer
rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using <productname>OpenSSL</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
index adb7363d63..3b4e2494a2 100644
--- a/src/test/ssl/sslfiles_nss.mk
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -43,7 +43,8 @@ NSSFILES := ssl/nss/client_ca.crt.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
+ ssl/nss/native_client-root.db \
+ ssl/nss/client_ext.crt__client_ext.key.db
nssfiles: $(NSSFILES)
@@ -168,6 +169,15 @@ ssl/nss/client.crt__client.key.db: ssl/client.crt
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 ''
+ssl/nss/client_ext.crt__client_ext.key.db: ssl/client_ext.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client_ext.crt -i ssl/client_ext.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client_ext.pfx -inkey ssl/client_ext.key -in ssl/client_ext.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client_ext.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) $@
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 4dc457e1b6..59ca8100c4 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,15 +14,7 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} eq 'openssl')
-{
- plan tests => 13;
-}
-elsif ($ENV{with_ssl} eq 'nss')
-{
- plan skip_all => 'SSL not supported by this build';
-}
-else
+if (($ENV{with_ssl} ne 'openssl') && ($ENV{with_ssl} ne 'nss'))
{
plan skip_all => 'SSL not supported by this build';
}
@@ -68,10 +60,11 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
# We aren't using any CRL's in this suite so we can keep using server-revoked
# as server certificate for simple client.crt connection much like how the
# 001 test does.
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR " .
+ "ssldatabase=ssl/nss/client_ext.crt__client_ext.key.db " .
"user=ssltestuser sslcert=ssl/client_ext.crt sslkey=$client_tmp_key";
# Make sure we can connect even though previous test suites have established this
@@ -110,32 +103,39 @@ $result = $node->safe_psql("certdb",
connstr => $common_connstr);
is($result, 't', "ssl_client_serial() compared with pg_stat_ssl");
-# Must not use safe_psql since we expect an error here
-$result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
- connstr => $common_connstr);
-is($result, '3', "ssl_client_dn_field() for an invalid field");
-
-$result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
- connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
- "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
-is($result, '', "ssl_client_dn_field() for connection without cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_client_dn_field() for commonName");
-
-$result = $node->safe_psql("certdb",
- "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_dn() for connection with cert");
-
-$result = $node->safe_psql("certdb",
- "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
- connstr => $common_connstr);
-is($result, 't', "ssl_issuer_field() for commonName");
+SKIP:
+{
+ skip "OpenSSL not supported by this build", 6 unless ($ENV{with_ssl} eq 'openssl');
+
+ # Must not use safe_psql since we expect an error here
+ $result = $node->psql("certdb", "SELECT ssl_client_dn_field('invalid');",
+ connstr => $common_connstr);
+ is($result, '3', "ssl_client_dn_field() for an invalid field");
+
+ $result = $node->safe_psql("trustdb", "SELECT ssl_client_dn_field('commonName');",
+ connstr => "sslrootcert=ssl/root+server_ca.crt sslmode=require " .
+ "dbname=trustdb hostaddr=$SERVERHOSTADDR user=ssltestuser");
+ is($result, '', "ssl_client_dn_field() for connection without cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_client_dn_field('commonName') = client_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_client_dn_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT ssl_issuer_dn() = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_dn() for connection with cert");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT '/CN=' || ssl_issuer_field('commonName') = issuer_dn FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+ connstr => $common_connstr);
+ is($result, 't', "ssl_issuer_field() for commonName");
+
+ $result = $node->safe_psql("certdb",
+ "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
+ connstr => $common_connstr);
+ is($result, 'CA:FALSE|t', 'extract extension from cert');
+}
-$result = $node->safe_psql("certdb",
- "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
- connstr => $common_connstr);
-is($result, 'CA:FALSE|t', 'extract extension from cert');
+done_testing();
--
2.24.3 (Apple Git-128)
v53-0007-nss-Support-NSS-in-pgcrypto.patchapplication/octet-stream; name=v53-0007-nss-Support-NSS-in-pgcrypto.patch; x-unix-mode=0644Download
From 9f0e5d3fdfc4bd461422e74f6660fdeb858568eb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v53 07/11] nss: Support NSS in pgcrypto
This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
contrib/pgcrypto/Makefile | 2 +
contrib/pgcrypto/expected/blowfish_2.out | 62 ++
contrib/pgcrypto/expected/cast5_2.out | 33 +
contrib/pgcrypto/expected/pgp-decrypt_2.out | 415 ++++++++++++
contrib/pgcrypto/expected/pgp-encrypt_1.out | 200 ++++++
.../expected/pgp-pubkey-decrypt_2.out | 632 ++++++++++++++++++
.../expected/pgp-pubkey-encrypt_1.out | 48 ++
contrib/pgcrypto/nss.c | 599 +++++++++++++++++
contrib/pgcrypto/openssl.c | 4 +
contrib/pgcrypto/pgp-mpi-nss.c | 53 ++
contrib/pgcrypto/pgp-mpi-openssl.c | 3 +
doc/src/sgml/pgcrypto.sgml | 10 +-
12 files changed, 2058 insertions(+), 3 deletions(-)
create mode 100644 contrib/pgcrypto/expected/blowfish_2.out
create mode 100644 contrib/pgcrypto/expected/cast5_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-encrypt_1.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
create mode 100644 contrib/pgcrypto/nss.c
create mode 100644 contrib/pgcrypto/pgp-mpi-nss.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 7fb59f51b7..645ba634a1 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -12,6 +12,7 @@ OBJS = \
crypt-gensalt.o \
crypt-md5.o \
mbuf.o \
+ nss.o \
openssl.o \
pgcrypto.o \
pgp-armor.o \
@@ -21,6 +22,7 @@ OBJS = \
pgp-encrypt.o \
pgp-info.o \
pgp-mpi.o \
+ pgp-mpi-nss.o \
pgp-mpi-openssl.o \
pgp-pgsql.o \
pgp-pubdec.o \
diff --git a/contrib/pgcrypto/expected/blowfish_2.out b/contrib/pgcrypto/expected/blowfish_2.out
new file mode 100644
index 0000000000..5b8b55b84f
--- /dev/null
+++ b/contrib/pgcrypto/expected/blowfish_2.out
@@ -0,0 +1,62 @@
+--
+-- Blowfish cipher
+--
+-- some standard Blowfish testvalues
+SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- setkey
+SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- with padding
+SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb');
+ERROR: Cannot use "bf-ecb": No such cipher algorithm
+-- cbc
+-- 28 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5',
+ '\x37363534333231204e6f77206973207468652074696d6520666f7220',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- 29 bytes key
+SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc',
+ '\x37363534333231204e6f77206973207468652074696d6520666f722000',
+ 'bf-cbc');
+ERROR: Cannot use "bf-cbc": No such cipher algorithm
+-- blowfish-448
+SELECT encrypt('\xfedcba9876543210',
+ '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff',
+ 'bf-ecb/pad:none');
+ERROR: Cannot use "bf-ecb/pad:none": No such cipher algorithm
+-- empty data
+select encrypt('', 'foo', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- 22 bytes key
+select encrypt('foo', '0123456789012345678901', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'bf');
+ERROR: Cannot use "bf": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape');
+ERROR: Cannot use "bf": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/cast5_2.out b/contrib/pgcrypto/expected/cast5_2.out
new file mode 100644
index 0000000000..5e829609b0
--- /dev/null
+++ b/contrib/pgcrypto/expected/cast5_2.out
@@ -0,0 +1,33 @@
+--
+-- Cast5 cipher
+--
+-- test vectors from RFC2144
+-- 128 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 80 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- 40 bit key
+SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none');
+ERROR: Cannot use "cast5-ecb/pad:none": No such cipher algorithm
+-- cbc
+-- empty data
+select encrypt('', 'foo', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- 10 bytes key
+select encrypt('foo', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- decrypt
+select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- iv
+select encrypt_iv('foo', '0123456', 'abcd', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
+-- long message
+select encrypt('Lets try a longer message.', '0123456789', 'cast5');
+ERROR: Cannot use "cast5": No such cipher algorithm
+select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape');
+ERROR: Cannot use "cast5": No such cipher algorithm
diff --git a/contrib/pgcrypto/expected/pgp-decrypt_2.out b/contrib/pgcrypto/expected/pgp-decrypt_2.out
new file mode 100644
index 0000000000..6ff774b190
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-decrypt_2.out
@@ -0,0 +1,415 @@
+--
+-- pgp decrypt tests
+--
+-- Checking ciphers
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.blowfish.sha1.mdc.s2k3.z0
+
+jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
+yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
+=JcP+
+-----END PGP MESSAGE-----
+'), 'foobar');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
+UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
+=XtrP
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
+5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
+=rCZt
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
+lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
+=fB6S
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking MDC modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.nomdc.s2k3.z0
+
+jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
+u9YkgfJfsuRJmgQ9tmo=
+=60ui
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
+8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
+=moGf
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking hashes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.md5.mdc.s2k3.z0
+
+jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
+KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
+=NyLk
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
+/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
+=FxbQ
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking S2K modes
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k0.z0
+
+jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
+Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
+=YvkV
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k1.z0
+
+jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
+4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
+=dbXm
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
+z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
+=VJKg
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k0.z0
+
+jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
+Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
+=cg+i
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k1.z0
+
+jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
+7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
+=aHmC
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes192.sha1.mdc.s2k3.z0
+
+jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
+q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
+=K0LS
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k0.z0
+
+jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
+rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
+=RGts
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k1.z0
+
+jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
+=SUrU
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes256.sha1.mdc.s2k3.z0
+
+jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
+4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
+=XZrG
+-----END PGP MESSAGE-----
+'), 'foobar');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking longer passwords
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
+tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
+=XKKG
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
+CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
+=gWDh
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
+FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
+=OxOF
+-----END PGP MESSAGE-----
+'), 'x');
+ pgp_sym_decrypt
+-----------------
+ Secret message.
+(1 row)
+
+-- Checking various data
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat1.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
+Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
+=W/ik
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x0225e3ede6f2587b076d021a189ff60aad67e066
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat2.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
+SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
+=Fxen
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \xda39a3ee5e6b4b0d3255bfef95601890afd80709
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: dat3.aes.sha1.mdc.s2k3.z0
+
+jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
+gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
+73Hb8m1yRhQK
+=ivrD
+-----END PGP MESSAGE-----
+'), '0123456789abcdefghij'), 'sha1');
+ digest
+--------------------------------------------
+ \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
+(1 row)
+
+-- Checking CRLF
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=0'), 'sha1');
+ digest
+--------------------------------------------
+ \x9353062be7720f1446d30b9e75573a4833886784
+(1 row)
+
+select digest(pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+Comment: crlf mess
+
+ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
+a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
+=mBP9
+-----END PGP MESSAGE-----
+'), 'key', 'convert-crlf=1'), 'sha1');
+ digest
+--------------------------------------------
+ \x7efefcab38467f7484d6fa43dc86cf5281bd78e2
+(1 row)
+
+-- check BUG #11905, problem with messages 6 less than a power of 2.
+select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
+ ?column?
+----------
+ t
+(1 row)
+
+-- Negative tests
+-- Decryption with a certain incorrect key yields an apparent Literal Data
+-- packet reporting its content to be binary data. Ciphertext source:
+-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
+-- rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
+VsxxqLSPzNLAeIspJk5G
+=mSd/
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_literal_data: data type=b
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine text/binary mismatch.
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
+NOTICE: dbg: parse_literal_data: data type=b
+ERROR: Not text data
+-- Decryption with a certain incorrect key yields an apparent BZip2-compressed
+-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
+-- until the random prefix gave rise to that property.
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
+GXsd65oYJZp3Khz0qfyn
+=Nmpq
+-----END PGP MESSAGE-----
+'), 'wrong-key', 'debug=1');
+NOTICE: dbg: prefix_init: corrupt prefix
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
+ERROR: Wrong key or corrupt data
+-- Routine use of BZip2 compression. Ciphertext source:
+-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
+-- --personal-cipher-preferences aes --no-emit-version --batch \
+-- --symmetric --passphrase key --armor
+select pgp_sym_decrypt(dearmor('
+-----BEGIN PGP MESSAGE-----
+
+jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
+QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
+UCAAw2JRIISttRHMfDpDuZJpvYo=
+=AZ9M
+-----END PGP MESSAGE-----
+'), 'key', 'debug=1');
+NOTICE: dbg: parse_compressed_data: bzip2 unsupported
+ERROR: Unsupported compression algorithm
diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out
new file mode 100644
index 0000000000..168dc9bafa
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out
@@ -0,0 +1,200 @@
+--
+-- PGP encrypt
+--
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- check whether the defaults are ok
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=aes128,
+ expect-disable-mdc=0,
+ expect-sess-key=0,
+ expect-s2k-mode=3,
+ expect-s2k-digest-algo=sha1,
+ expect-compress-algo=0
+ ');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- maybe the expect- stuff simply does not work
+select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
+ 'key', 'expect-cipher-algo=bf,
+ expect-disable-mdc=1,
+ expect-sess-key=1,
+ expect-s2k-mode=0,
+ expect-s2k-digest-algo=md5,
+ expect-compress-algo=1
+ ');
+NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
+NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
+NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
+NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
+NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- bytea as text
+select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
+ERROR: Not text data
+-- text as bytea
+select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape');
+ encode
+--------
+ Text
+(1 row)
+
+-- algorithm change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
+ 'key', 'expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
+ 'key', 'expect-cipher-algo=aes128');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
+ 'key', 'expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
+ 'key', 'expect-s2k-mode=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
+ 'key', 'expect-s2k-mode=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
+ 'key', 'expect-s2k-mode=3');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k count change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
+ 'key', 'expect-s2k-count=1024');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k_count rounds up
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
+ 'key', 'expect-s2k-count=65000000');
+NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- s2k digest change
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
+ 'key', 'expect-s2k-digest-algo=md5');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
+ 'key', 'expect-s2k-digest-algo=sha1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- sess key
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
+ 'key', 'expect-sess-key=0');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
+ 'key', 'expect-sess-key=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
+ERROR: Unsupported cipher algorithm
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
+ 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- no mdc
+select pgp_sym_decrypt(
+ pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
+ 'key', 'expect-disable-mdc=1');
+ pgp_sym_decrypt
+-----------------
+ Secret.
+(1 row)
+
+-- crlf
+select pgp_sym_decrypt_bytea(
+ pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
+ 'key');
+ pgp_sym_decrypt_bytea
+------------------------
+ \x310d0a320d0a330d0d0a
+(1 row)
+
+-- conversion should be lossless
+select digest(pgp_sym_decrypt(
+ pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
+ 'key', 'convert-crlf=1'), 'sha1') as result,
+ digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect;
+ result | expect
+--------------------------------------------+--------------------------------------------
+ \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966
+(1 row)
+
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
new file mode 100644
index 0000000000..6eb8ad8525
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_2.out
@@ -0,0 +1,632 @@
+--
+-- PGP Public Key Encryption
+--
+-- As most of the low-level stuff is tested in symmetric key
+-- tests, here's only public-key specific tests
+create table keytbl (
+ id int4,
+ name text,
+ pubkey text,
+ seckey text
+);
+create table encdata (
+ id int4,
+ data text
+);
+insert into keytbl (id, name, pubkey, seckey)
+values (1, 'elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
+WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
+XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
+ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
+ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
+m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
+iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
+LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
+a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
+VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
+m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
+7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
+=nvqq
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (2, 'elg2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
+MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
+AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
+or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
+AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
+MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
+QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
+gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
+LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
+k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
+bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
+Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
+QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
+Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
+Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
+6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
+1kNTmEU=
+=8QM5
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
+vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
+5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
+RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
+8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
+Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
+J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
+T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
+0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
+8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
+cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
+Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
+n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
+29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
+PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
+kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
+VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
+gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
+frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
+VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
+NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
+7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
+fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
+dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
+EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
+3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
+=3Dgk
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (3, 'elg4096', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=kRxT
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=Gjq6
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (4, 'rsa2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
+OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
+HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
+nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
+bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
+D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
+9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
+S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
+=lR4n
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
+ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
+55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
+5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
+MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
+R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
+fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
+D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
+5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
+5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
+7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
+GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
+2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
+TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
+St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
+WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
+V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
+89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
+9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
+Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
+AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
+DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
+GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
+uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
+NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
+rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
+fDs/qQ9dZhtEmDbKPLTVEA==
+=WKAv
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (5, 'psw-elg1024', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
+MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
+AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
+ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
+sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
+6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
+k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
+iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
+ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
+=RWci
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
+tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
+xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
+klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
+YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
+PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
+jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
+saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
+IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
+D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
+ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
+ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
+2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
+BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
+BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
+B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
+d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
+qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
+mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
+9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
+f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
+IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
+=SNvr
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (6, 'rsaenc2048', '
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
+UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
+czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
+4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
+dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
+NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
+YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
+AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
+JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
+UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
+RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
+0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
+QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
+z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
+lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
+FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
+rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
+JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
+yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
+WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
+w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
+dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
+PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
+CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
+=pwU2
+-----END PGP PUBLIC KEY BLOCK-----
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+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
+=UKh3
+-----END PGP PRIVATE KEY BLOCK-----
+');
+insert into keytbl (id, name, pubkey, seckey)
+values (7, 'rsaenc2048-psw', '
+same key with password
+', '
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+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
+=QFm5
+-----END PGP PRIVATE KEY BLOCK-----
+');
+-- elg1024 / aes128
+insert into encdata (id, data) values (1, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
+faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
+e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
+/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
+PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
+uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
+wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
+=PABx
+-----END PGP MESSAGE-----
+');
+-- elg2048 / blowfish
+insert into encdata (id, data) values (2, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
+Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
+vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
+OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
+e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
+m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
+QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
+iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
+YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
+YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
+p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
+G2pAE+3k
+=TBHV
+-----END PGP MESSAGE-----
+');
+-- elg4096 / aes256
+insert into encdata (id, data) values (3, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
+SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
+Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
+z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
+1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
+ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
+nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
+2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
+C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
+D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
+/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
+txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
+WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
+6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
+KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
+ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
+0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
+3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
+huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
+exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
+2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
+DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
+=L/M/
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128
+insert into encdata (id, data) values (4, '
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.1 (GNU/Linux)
+
+hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
+pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
+DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
+yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
+VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
+HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
+eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
+GQ==
+=XHkF
+-----END PGP MESSAGE-----
+');
+-- rsaenc2048 / aes128 (not from gnupg)
+insert into encdata (id, data) values (5, '
+-----BEGIN PGP MESSAGE-----
+
+wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
+p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
+7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
+C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
+pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
+3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
+5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
+vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
+/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
+7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
+tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
+g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
+Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
+blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
+=PHJ1
+-----END PGP MESSAGE-----
+');
+-- successful decrypt
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=1 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=3 and encdata.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- wrong key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=2 and encdata.id=1;
+ERROR: Wrong key
+-- sign-only key
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=4 and encdata.id=1;
+ERROR: No encryption key found
+-- rsa: password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Wrong key or corrupt data
+-- rsa: password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=7 and encdata.id=4;
+ERROR: Unsupported public key algorithm
+-- password-protected secret key, no password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Need password for secret key
+-- password-protected secret key, wrong password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Wrong key or corrupt data
+-- password-protected secret key, right password
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
+from keytbl, encdata where keytbl.id=5 and encdata.id=1;
+ERROR: Unsupported public key algorithm
+-- test for a short read from prefix_init
+select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
+from keytbl, encdata where keytbl.id=6 and encdata.id=5;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
new file mode 100644
index 0000000000..a5dd6d092c
--- /dev/null
+++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt_1.out
@@ -0,0 +1,48 @@
+--
+-- PGP Public Key Encryption
+--
+-- successful encrypt/decrypt
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=2;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=3;
+ERROR: Unsupported public key algorithm
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=6;
+ERROR: Unsupported public key algorithm
+-- try with rsa-sign only
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=4;
+ERROR: No encryption key found
+-- try with secret key
+select pgp_pub_decrypt(
+ pgp_pub_encrypt('Secret msg', dearmor(seckey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Refusing to encrypt with secret key
+-- does text-to-bytea works
+select encode(pgp_pub_decrypt_bytea(
+ pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
+ dearmor(seckey)), 'escape')
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
+-- and bytea-to-text?
+select pgp_pub_decrypt(
+ pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
+ dearmor(seckey))
+from keytbl where keytbl.id=1;
+ERROR: Unsupported public key algorithm
diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c
new file mode 100644
index 0000000000..8cbb9762b5
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,599 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+#ifdef USE_NSS
+
+#include "px.h"
+#include "common/pg_nss.h"
+#include "utils/memutils.h"
+
+#include <pk11func.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <sechash.h>
+#include <secoid.h>
+#include <secerr.h>
+
+/*
+ * Data structures for recording cipher implementations as well as ongoing
+ * cipher operations.
+ */
+typedef struct nss_digest
+{
+ NSSInitContext *context;
+ PK11Context *hash_context;
+ HASH_HashType hash_type;
+} nss_digest;
+
+typedef struct cipher_implementation
+{
+ /* Function pointers to cipher operations */
+ int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv);
+ unsigned (*get_block_size) (PX_Cipher *pxc);
+ unsigned (*get_key_size) (PX_Cipher *pxc);
+ unsigned (*get_iv_size) (PX_Cipher *pxc);
+ int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res);
+ void (*free) (PX_Cipher *pxc);
+
+ /* The mechanism describing the cipher used */
+ CK_MECHANISM_TYPE mechanism;
+ int keylen;
+} 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 nss_cipher_ref
+{
+ const char *name;
+ const cipher_implementation *impl;
+} nss_cipher_ref;
+
+/*
+ * Prototypes
+ */
+static unsigned nss_get_iv_size(PX_Cipher *pxc);
+static unsigned nss_get_block_size(PX_Cipher *pxc);
+
+/*
+ * nss_GetHashOidTagByHashType
+ *
+ * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43
+ * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of
+ * writing is not commonly available so we need our own version till then.
+ */
+static SECOidTag
+nss_GetHashOidTagByHashType(HASH_HashType type)
+{
+ if (type == HASH_AlgMD2)
+ return SEC_OID_MD2;
+ if (type == HASH_AlgMD5)
+ return SEC_OID_MD5;
+ if (type == HASH_AlgSHA1)
+ return SEC_OID_SHA1;
+ if (type == HASH_AlgSHA224)
+ return SEC_OID_SHA224;
+ if (type == HASH_AlgSHA256)
+ return SEC_OID_SHA256;
+ if (type == HASH_AlgSHA384)
+ return SEC_OID_SHA384;
+ if (type == HASH_AlgSHA512)
+ return SEC_OID_SHA512;
+
+ return SEC_OID_UNKNOWN;
+}
+
+static unsigned
+nss_digest_block_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->blocklength;
+}
+
+static unsigned
+nss_digest_result_size(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ return object->length;
+}
+
+static void
+nss_digest_free(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_DestroyContext(digest->hash_context, free_ctx);
+ NSS_ShutdownContext(digest->context);
+}
+
+static void
+nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestOp(digest->hash_context, data, dlen);
+}
+
+static void
+nss_digest_reset(PX_MD *pxmd)
+{
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+
+ PK11_DigestBegin(digest->hash_context);
+}
+
+static void
+nss_digest_finish(PX_MD *pxmd, uint8 *dst)
+{
+ unsigned int outlen;
+ nss_digest *digest = (nss_digest *) pxmd->p.ptr;
+ const SECHashObject *object;
+
+ object = HASH_GetHashObject(digest->hash_type);
+ PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length);
+}
+
+int
+px_find_digest(const char *name, PX_MD **res)
+{
+ PX_MD *pxmd;
+ NSSInitParameters params;
+ NSSInitContext *nss_context;
+ bool found = false;
+ nss_digest *digest;
+ SECStatus status;
+ SECOidData *hash;
+ HASH_HashType t;
+
+ /*
+ * Initialize our own NSS context without a database backing it.
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ nss_context = 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 = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++)
+ {
+ SECOidTag hash_oid;
+ char *p;
+
+ hash_oid = nss_GetHashOidTagByHashType(t);
+
+ if (hash_oid == SEC_OID_UNKNOWN)
+ return PXE_NO_HASH;
+
+ hash = SECOID_FindOIDByTag(hash_oid);
+ if (pg_strcasecmp(hash->desc, name) == 0)
+ {
+ found = true;
+ break;
+ }
+
+ /*
+ * NSS saves the algorithm names using SHA-xxx notation whereas
+ * OpenSSL use SHAxxx. To make sure the user finds the requested
+ * algorithm let's remove the dash and compare that spelling as well.
+ */
+ if ((p = strchr(hash->desc, '-')) != NULL)
+ {
+ char tmp[12];
+
+ memcpy(tmp, hash->desc, p - hash->desc);
+ memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1);
+ if (pg_strcasecmp(tmp, name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return PXE_NO_HASH;
+
+ digest = palloc(sizeof(*digest));
+
+ digest->context = nss_context;
+ digest->hash_context = PK11_CreateDigestContext(hash->offset);
+ digest->hash_type = t;
+ if (digest->hash_context == NULL)
+ {
+ pfree(digest);
+ return -1;
+ }
+
+ status = PK11_DigestBegin(digest->hash_context);
+ if (status != SECSuccess)
+ {
+ PK11_DestroyContext(digest->hash_context, PR_TRUE);
+ pfree(digest);
+ return -1;
+ }
+
+ pxmd = palloc(sizeof(*pxmd));
+ pxmd->result_size = nss_digest_result_size;
+ pxmd->block_size = nss_digest_block_size;
+ pxmd->reset = nss_digest_reset;
+ pxmd->update = nss_digest_update;
+ pxmd->finish = nss_digest_finish;
+ pxmd->free = nss_digest_free;
+ pxmd->p.ptr = (void *) digest;
+
+ *res = pxmd;
+
+ return 0;
+}
+
+static int
+nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen,
+ const uint8 *iv)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECItem iv_item;
+ unsigned char *iv_item_data;
+ SECItem key_item;
+ int keylen;
+ PK11SlotInfo *slot;
+
+ if (cipher->impl->mechanism == CKM_AES_CBC ||
+ cipher->impl->mechanism == CKM_AES_ECB)
+ {
+ if (klen <= 128 / 8)
+ keylen = 128 / 8;
+ else if (klen <= 192 / 8)
+ keylen = 192 / 8;
+ else if (klen <= 256 / 8)
+ keylen = 256 / 8;
+ else
+ return PXE_CIPHER_INIT;
+ }
+ else
+ keylen = cipher->impl->keylen;
+
+ key_item.type = siBuffer;
+ key_item.data = (unsigned char *) key;
+ key_item.len = keylen;
+
+ /*
+ * If hardware acceleration is configured in NSS one can theoretically get
+ * a better slot by calling PK11_GetBestSlot() with the mechanism passed
+ * to it. Unless there are complaints, using the simpler API will make
+ * error handling easier though.
+ */
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ return PXE_CIPHER_INIT;
+
+ /*
+ * The key must be set up for the operation, and since we don't know at
+ * this point whether we are asked to encrypt or decrypt we need to store
+ * both versions.
+ */
+ cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item,
+ NULL);
+ cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism,
+ PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item,
+ NULL);
+ PK11_FreeSlot(slot);
+
+ if (!cipher->decrypt_key || !cipher->encrypt_key)
+ return PXE_CIPHER_INIT;
+
+ if (iv)
+ {
+ iv_item.type = siBuffer;
+ iv_item.data = (unsigned char *) iv;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+ else
+ {
+ /*
+ * The documentation states that either passing .data = 0; .len = 0;
+ * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be
+ * done when IV is missing. That however leads to segfaults in the
+ * library, the workaround that works in modern library versions is
+ * to pass in a keysized zeroed out IV.
+ */
+ iv_item_data = palloc0(nss_get_iv_size(pxc));
+ iv_item.type = siBuffer;
+ iv_item.data = iv_item_data;
+ iv_item.len = nss_get_iv_size(pxc);
+ }
+
+ cipher->params = PK11_ParamFromIV(cipher->impl->mechanism, &iv_item);
+
+ /* If we had to make a mock IV, free it once made into a param */
+ if (!iv)
+ pfree(iv_item_data);
+
+ if (cipher->params == NULL)
+ return PXE_CIPHER_INIT;
+
+ return 0;
+}
+
+static int
+nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_DECRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static int
+nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res)
+{
+ nss_cipher *cipher = (nss_cipher *) pxc->ptr;
+ SECStatus status;
+ int outlen;
+
+ if (!cipher->crypt_context)
+ {
+ cipher->crypt_context =
+ PK11_CreateContextBySymKey(cipher->impl->mechanism, CKA_ENCRYPT,
+ cipher->decrypt_key, cipher->params);
+ }
+
+ status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen);
+
+ if (status != SECSuccess)
+ return PXE_DECRYPT_FAILED;
+
+ return 0;
+}
+
+static void
+nss_free(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+ PRBool free_ctx = PR_TRUE;
+
+ PK11_FreeSymKey(cipher->encrypt_key);
+ PK11_FreeSymKey(cipher->decrypt_key);
+ PK11_DestroyContext(cipher->crypt_context, free_ctx);
+ NSS_ShutdownContext(cipher->context);
+ pfree(cipher);
+
+ pfree(pxc);
+}
+
+static unsigned
+nss_get_block_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetBlockSize(cipher->impl->mechanism, NULL);
+}
+
+static unsigned
+nss_get_key_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return cipher->impl->keylen;
+}
+
+static unsigned
+nss_get_iv_size(PX_Cipher *pxc)
+{
+ nss_cipher *cipher = pxc->ptr;
+
+ return PK11_GetIVLength(cipher->impl->mechanism);
+}
+
+/*
+ * Cipher Implementations
+ */
+static const cipher_implementation nss_des_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_CBC,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES_ECB,
+ .keylen = 8,
+};
+
+static const cipher_implementation nss_des3_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_CBC,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_des3_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_DES3_ECB,
+ .keylen = 24,
+};
+
+static const cipher_implementation nss_aes_cbc = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_CBC,
+ .keylen = 32,
+};
+
+static const cipher_implementation nss_aes_ecb = {
+ .init = nss_symkey_blockcipher_init,
+ .get_block_size = nss_get_block_size,
+ .get_key_size = nss_get_key_size,
+ .get_iv_size = nss_get_iv_size,
+ .encrypt = nss_encrypt,
+ .decrypt = nss_decrypt,
+ .free = nss_free,
+ .mechanism = CKM_AES_ECB,
+ .keylen = 32,
+};
+
+/*
+ * Lookup table for finding the implementation based on a name. CAST5 as well
+ * as BLOWFISH are defined as cipher mechanisms in NSS but error out with
+ * SEC_ERROR_INVALID_ALGORITHM.
+ */
+static const nss_cipher_ref nss_cipher_lookup[] = {
+ {"des", &nss_des_cbc},
+ {"des-cbc", &nss_des_cbc},
+ {"des-ecb", &nss_des_ecb},
+ {"des3-cbc", &nss_des3_cbc},
+ {"3des", &nss_des3_cbc},
+ {"3des-cbc", &nss_des3_cbc},
+ {"des3-ecb", &nss_des3_ecb},
+ {"3des-ecb", &nss_des3_ecb},
+ {"aes", &nss_aes_cbc},
+ {"aes-cbc", &nss_aes_cbc},
+ {"aes-ecb", &nss_aes_ecb},
+ {"rijndael", &nss_aes_cbc},
+ {"rijndael-cbc", &nss_aes_cbc},
+ {"rijndael-ecb", &nss_aes_ecb},
+ {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;
+
+ for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++)
+ {
+ if (strcmp(cipher_ref->name, alias) == 0)
+ break;
+ }
+
+ if (!cipher_ref->name)
+ return PXE_NO_CIPHER;
+
+ /*
+ * Fill in the PX_Cipher to pass back to PX describing the operations to
+ * perform in order to use the cipher.
+ */
+ px_cipher = palloc(sizeof(*px_cipher));
+ px_cipher->block_size = cipher_ref->impl->get_block_size;
+ px_cipher->key_size = cipher_ref->impl->get_key_size;
+ px_cipher->iv_size = cipher_ref->impl->get_iv_size;
+ px_cipher->free = cipher_ref->impl->free;
+ px_cipher->init = cipher_ref->impl->init;
+ px_cipher->encrypt = cipher_ref->impl->encrypt;
+ px_cipher->decrypt = cipher_ref->impl->decrypt;
+
+ /*
+ * 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 = sizeof(params);
+ nss_context = 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 = palloc(sizeof(*cipher));
+ cipher->context = nss_context;
+ cipher->impl = cipher_ref->impl;
+ cipher->crypt_context = NULL;
+
+ px_cipher->ptr = cipher;
+
+ *res = px_cipher;
+ return 0;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index e236b0d79c..7f6b15551b 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,8 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@@ -819,3 +821,5 @@ px_find_cipher(const char *name, PX_Cipher **res)
*res = c;
return 0;
}
+
+#endif /* USE_OPENSSL */
diff --git a/contrib/pgcrypto/pgp-mpi-nss.c b/contrib/pgcrypto/pgp-mpi-nss.c
new file mode 100644
index 0000000000..5f8192d695
--- /dev/null
+++ b/contrib/pgcrypto/pgp-mpi-nss.c
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgp-mpi-nss.c
+ * Wrapper for using NSS as a backend for pgcrypto PGP
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * contrib/pgcrypto/pgp-mpi-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#ifdef USE_NSS
+
+#include "pgp.h"
+#include "px.h"
+
+/*
+ * TODO: There is no exported BIGNUM library in NSS mapping to the OpenSSL
+ * counterpart so for now this isn't supported when using NSS as a backend.
+ */
+int
+pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m,
+ PGP_MPI **c1_p, PGP_MPI **c2_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2,
+ PGP_MPI **msg_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+int
+pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p)
+{
+ return PXE_PGP_UNSUPPORTED_PUBALGO;
+}
+
+#endif /* USE_NSS */
diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c
index 75e4c8b300..898f8a493e 100644
--- a/contrib/pgcrypto/pgp-mpi-openssl.c
+++ b/contrib/pgcrypto/pgp-mpi-openssl.c
@@ -30,6 +30,8 @@
*/
#include "postgres.h"
+#ifdef USE_OPENSSL
+
#include <openssl/bn.h>
#include "pgp.h"
@@ -282,3 +284,4 @@ err:
BN_clear_free(c);
return res;
}
+#endif /* USE_OPENSSL */
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 79759654a7..2e3fc914e1 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -765,7 +765,10 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
<title>cipher-algo</title>
<para>
- Which cipher algorithm to use.
+ Which cipher algorithm to use. <literal>cast5</literal>, and
+ <literal>blowfish</literal> are only available
+ if <productname>PostgreSQL</productname> was built with
+ <productname>OpenSSL</productname>.
</para>
<literallayout>
Values: bf, aes128, aes192, aes256, 3des, cast5
@@ -1157,8 +1160,8 @@ gen_random_uuid() returns uuid
<para>
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
- affect it are <literal>--with-zlib</literal> and
- <literal>--with-ssl=openssl</literal>.
+ affect it are <literal>--with-zlib</literal>,
+ <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
</para>
<para>
@@ -1177,6 +1180,7 @@ gen_random_uuid() returns uuid
<filename>openssl.cnf</filename> configuration file in order to use older
ciphers like DES or Blowfish.
</para>
+
</sect3>
<sect3>
--
2.24.3 (Apple Git-128)
v53-0006-nss-Documentation.patchapplication/octet-stream; name=v53-0006-nss-Documentation.patch; x-unix-mode=0644Download
From bef88808c041da8a6d435237844d6fb9f3baa418 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:42 +0100
Subject: [PATCH v53 06/11] 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 | 34 +++-
doc/src/sgml/libpq.sgml | 341 +++++++++++++++++++++++++--------
doc/src/sgml/runtime.sgml | 124 +++++++++++-
src/backend/libpq/README.SSL | 28 +++
6 files changed, 497 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml
index 9ed148ab84..4da20cb4d3 100644
--- a/doc/src/sgml/acronyms.sgml
+++ b/doc/src/sgml/acronyms.sgml
@@ -452,6 +452,28 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>NSPR</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR">
+ Netscape Portable Runtime</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><acronym>NSS</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS">
+ Network Security Services</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>ODBC</acronym></term>
<listitem>
@@ -550,6 +572,17 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><acronym>PKCS#12</acronym></term>
+ <listitem>
+ <para>
+ <ulink
+ url="https://en.wikipedia.org/wiki/PKCS_12">
+ Public-Key Cryptography Standards #12</ulink>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><acronym>PL</acronym></term>
<listitem>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 692d8a2a17..bc3db6ce70 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1310,6 +1310,24 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-database" xreflabel="ssl_database">
+ <term><varname>ssl_database</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_database</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the server certificates and
+ keys when using <productname>NSS</productname> for
+ <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ connections. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command
+ line.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1326,7 +1344,9 @@ include_dir 'conf.d'
connections using TLS version 1.2 and lower are affected. There is
currently no setting that controls the cipher choices used by TLS
version 1.3 connections. The default value is
- <literal>HIGH:MEDIUM:+3DES:!aNULL</literal>. The default is usually a
+ <literal>HIGH:MEDIUM:+3DES:!aNULL</literal> for servers which have
+ been built with <productname>OpenSSL</productname> as the
+ <acronym>SSL</acronym> library. The default is usually a
reasonable choice unless you have specific security requirements.
</para>
@@ -1538,8 +1558,12 @@ include_dir 'conf.d'
<para>
Sets an external command to be invoked when a passphrase for
decrypting an SSL file such as a private key needs to be obtained. By
- default, this parameter is empty, which means the built-in prompting
- mechanism is used.
+ default, this parameter is empty. When the server is using
+ <productname>OpenSSL</productname>, this means the built-in prompting
+ mechanism is used. When using <productname>NSS</productname>, there is
+ no default prompting so a blank callback will be used returning an
+ empty password. This requires that the certificate database hasn't
+ been created with a password.
</para>
<para>
The command must print the passphrase to the standard output and exit
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 655095f3b1..841e835094 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -999,14 +999,34 @@ build-postgresql:
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. The only <replaceable>LIBRARY</replaceable>
- supported is <option>openssl</option>. This requires the
- <productname>OpenSSL</productname> package to be installed.
- <filename>configure</filename> will check for the required
- header files and libraries to make sure that your
- <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections. <replaceable>LIBRARY</replaceable> must be one of:
</para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to build with <productname>OpenSSL</productname> support.
+ This requires the <productname>OpenSSL</productname>
+ package to be installed. <filename>configure</filename> will check
+ for the required header files and libraries to make sure
+ your <productname>OpenSSL</productname> installation is sufficient
+ before proceeding.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>nss</option> to build with <productname>libnss</productname> support.
+ This requires the <productname>NSS</productname> package to be installed.
+ Additionally, <productname>NSS</productname> requires
+ <productname>NSPR</productname> to be installed. <filename>configure</filename>
+ will check for the required header files and libraries to make sure that your
+ <productname>NSS</productname> installation is sufficient before
+ proceeding. On systems where <application>nss-config</application>
+ is not installed in a location that is searched by default, you
+ must use the option <option>--with-includes</option> in addition
+ to this option to indicate where the header files are located.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index e0ab7cd555..fd1dee030f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -835,6 +835,12 @@ int callback_fn(char *buf, int size, PGconn *conn);
<function>longjmp(...)</function>, etc. It must return normally.
</para>
+ <para>
+ <function>PQsetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
</listitem>
</varlistentry>
@@ -851,6 +857,70 @@ PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
</synopsis>
</para>
+ <para>
+ <function>PQgetSSLKeyPassHook_OpenSSL</function> has no effect unless
+ the server was compiled with <productname>OpenSSL</productname>
+ support.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqsetsslkeypasshook-nss">
+ <term><function>PQsetSSLKeyPassHook_nss</function><indexterm><primary>PQsetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> lets an application override
+ <application>libpq</application>'s default handling of password protected
+ objects in the <application>NSS</application> database using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+<programlisting>
+char *callback_fn(PK11SlotInfo *slot, PRBool retry, void *arg);
+</programlisting>
+ which <application>libpq</application> will call
+ <emphasis>instead of</emphasis> its default
+ <function>PQdefaultSSLKeyPassHook_nss</function> password handler. The
+ callback should determine the password for the token and return a
+ pointer to it. The returned pointer should be allocated with the NSS
+ <function>PORT_Strdup</function> function. The token for which the
+ password is requested is recorded in the slot anc can be identified by
+ calling <function>PK11_GetTokenName</function>. If no password is
+ known, the callback should return <literal>NULL</literal>. The memory
+ will be owned by <productname>NSS</productname> and should not be
+ freed.
+ </para>
+
+ <para>
+ <function>PQsetSSLKeyPassHook_nss</function> has no effect unless
+ the server was compiled with <productname>NSS</productname> support.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook-nss">
+ <term><function>PQgetSSLKeyPassHook_nss</function><indexterm><primary>PQgetSSLKeyPassHook_nss</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> returns the current
+ <productname>NSS</productname> password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+</synopsis>
+ </para>
+
+ <para>
+ <function>PQgetSSLKeyPassHook_nss</function> has no effect unless the
+ server was compiled with <productname>NSS</productname> support.
+ </para>
+
</listitem>
</varlistentry>
@@ -1643,23 +1713,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
If set to 1, data sent over SSL connections will be compressed. If
set to 0, compression will be disabled. The default is 0. This
parameter is ignored if a connection without SSL is made.
- </para>
-
- <para>
SSL compression is nowadays considered insecure and its use is no
- longer recommended. <productname>OpenSSL</productname> 1.1.0 disables
- compression by default, and many operating system distributions
- disable it in prior versions as well, so setting this parameter to on
- will not have any effect if the server does not accept compression.
- <productname>PostgreSQL</productname> 14 disables compression
- completely in the backend.
- </para>
+ longer recommended.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: 1.1.0 disables compression by
+ default, and many operating system distributions disable it in prior
+ versions as well, so setting this parameter to on will not have any
+ effect if the server does not accept compression.
+ </para>
+ </listitem>
- <para>
- If security is not a primary concern, compression can improve
- throughput if the network is the bottleneck. Disabling compression
- can improve response time and throughput if CPU performance is the
- limiting factor.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: SSL compression is not supported in
+ <productname>NSS</productname>, this parameter has no effect.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1668,10 +1740,24 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcert</literal></term>
<listitem>
<para>
- This parameter specifies the file name of the client SSL
- certificate, replacing the default
- <filename>~/.postgresql/postgresql.crt</filename>.
- This parameter is ignored if an SSL connection is not made.
+ This parameter specifies the name or location of the client SSL
+ certificate. This parameter is ignored if an SSL connection is not
+ made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can replace the default
+ <filename>~/.postgresql/postgresql.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: the nickname of the client SSL
+ certificate.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1680,15 +1766,30 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslkey</literal></term>
<listitem>
<para>
- This parameter specifies the location for the secret key used for
- the client certificate. It can either specify a file name that will
- be used instead of the default
- <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
- obtained from an external <quote>engine</quote> (engines are
- <productname>OpenSSL</productname> loadable modules). An external engine
- specification should consist of a colon-separated engine name and
- an engine-specific key identifier. This parameter is ignored if an
- SSL connection is not made.
+ This parameter specifies the name or location for the secret key used
+ for the client certificate. This parameter is ignored if an SSL
+ connection is not made.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: can either specify a file name
+ that will be used instead of the default
+ <filename>~/.postgresql/postgresql.key</filename>, or it can specify
+ a key obtained from an external <quote>engine</quote> (engines are
+ <productname>OpenSSL</productname> loadable modules). An external
+ engine specification should consist of a colon-separated engine name
+ and an engine-specific key identifier.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. Keys
+ should be loaded into the <productname>NSS</productname>
+ database.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1699,28 +1800,44 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<para>
This parameter specifies the password for the secret key specified in
<literal>sslkey</literal>, allowing client certificate private keys
- to be stored in encrypted form on disk even when interactive passphrase
+ to be stored in encrypted form even when interactive passphrase
input is not practical.
- </para>
- <para>
- Specifying this parameter with any non-empty value suppresses the
- <literal>Enter PEM pass phrase:</literal>
- prompt that <productname>OpenSSL</productname> will emit by default
- when an encrypted client certificate key is provided to
- <literal>libpq</literal>.
- </para>
- <para>
- If the key is not encrypted this parameter is ignored. The parameter
- has no effect on keys specified by <productname>OpenSSL</productname>
- engines unless the engine uses the <productname>OpenSSL</productname>
- password callback mechanism for prompts.
- </para>
- <para>
- There is no environment variable equivalent to this option, and no
- facility for looking it up in <filename>.pgpass</filename>. It can be
- used in a service file connection definition. Users with
- more sophisticated uses should consider using <productname>OpenSSL</productname> engines and
- tools like PKCS#11 or USB crypto offload devices.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifying this parameter with
+ any non-empty value suppresses the
+ <literal>Enter PEM pass phrase:</literal> prompt that
+ <productname>OpenSSL</productname> will emit by default when an
+ encrypted client certificate key is provided to
+ <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter
+ has no effect on keys specified by <productname>OpenSSL</productname>
+ engines unless the engine uses the <productname>OpenSSL</productname>
+ password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using
+ <productname>OpenSSL</productname> engines and tools like PKCS#11 or
+ USB crypto offload devices.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: specifying the parameter is required
+ in case any password protected items are referenced in the
+ <productname>NSS</productname> database, or if the database itself
+ is password protected. All attempts to decrypt objects which are
+ password protected in the database will use this password.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1729,11 +1846,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslrootcert</literal></term>
<listitem>
<para>
- This parameter specifies the name of a file containing SSL
- certificate authority (<acronym>CA</acronym>) certificate(s).
- If the file exists, the server's certificate will be verified
- to be signed by one of these authorities. The default is
- <filename>~/.postgresql/root.crt</filename>.
+ This parameter specifies the name of SSL certificate authority
+ (<acronym>CA</acronym>) certificate(s).
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: specifies the name of a file
+ containing SSL certificate authority (<acronym>CA</acronym>)
+ certificate(s). If the file exists, the server's certificate will
+ be verified to be signed by one of these authorities. The default is
+ <filename>~/.postgresql/root.crt</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: this parameter has no effect. CA
+ certificates should be loaded into the <productname>NSS</productname>
+ database using the <application>certutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
</listitem>
</varlistentry>
@@ -1743,13 +1876,29 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<listitem>
<para>
This parameter specifies the file name of the SSL server certificate
- revocation list (CRL). Certificates listed in this file, if it
- exists, will be rejected while attempting to authenticate the
- server's certificate. If neither
- <xref linkend='libpq-connect-sslcrl'/> nor
- <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
- taken as
- <filename>~/.postgresql/root.crl</filename>.
+ revocation list (CRL).
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: Certificates listed in this file,
+ if it exists, will be rejected while attempting to authenticate the
+ server's certificate. If neither
+ <xref linkend='libpq-connect-sslcrl'/> nor
+ <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
+ taken as <filename>~/.postgresql/root.crl</filename>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
+
</para>
</listitem>
</varlistentry>
@@ -1762,19 +1911,31 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in the files in this
directory, if it exists, will be rejected while attempting to
authenticate the server's certificate.
- </para>
- <para>
- The directory needs to be prepared with the
- <productname>OpenSSL</productname> command
- <literal>openssl rehash</literal> or <literal>c_rehash</literal>. See
- its documentation for details.
- </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <productname>OpenSSL</productname>: the directory needs to be
+ prepared with the <productname>OpenSSL</productname> command
+ <literal>openssl rehash</literal> or <literal>c_rehash</literal>.
+ See its documentation for details.
+ </para>
+ <para>
+ Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can
+ be specified together.
+ </para>
+ </listitem>
- <para>
- Both <literal>sslcrl</literal> and <literal>sslcrldir</literal> can be
- specified together.
+ <listitem>
+ <para>
+ <productname>NSS</productname>: This parameter has no effect. CRL
+ files should be loaded into the <productname>NSS</productname>
+ database using the <application>crlutil</application> tool.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
+
</listitem>
</varlistentry>
@@ -1829,8 +1990,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not specified,
+ version of the SSL library used, older versions may
+ not support the most modern protocol versions. If not specified,
the default is <literal>TLSv1.2</literal>, which satisfies industry
best practices as of this writing.
</para>
@@ -1845,8 +2006,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
for the connection. Valid values are <literal>TLSv1</literal>,
<literal>TLSv1.1</literal>, <literal>TLSv1.2</literal> and
<literal>TLSv1.3</literal>. The supported protocols depend on the
- version of <productname>OpenSSL</productname> used, older versions
- not supporting the most modern protocol versions. If not set, this
+ version of the SSL library used, older versions
+ may not support the most modern protocol versions. If not set, this
parameter is ignored and the connection will use the maximum bound
defined by the backend, if set. Setting the maximum protocol version
is mainly useful for testing or if some component has issues working
@@ -2608,6 +2769,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
</para>
<para>
The struct(s) available depend on the SSL implementation in use.
+ </para>
+ <para>
For <productname>OpenSSL</productname>, there is one struct,
available under the name "OpenSSL", and it returns a pointer to the
<productname>OpenSSL</productname> <literal>SSL</literal> struct.
@@ -2631,9 +2794,15 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name);
]]></programlisting>
</para>
<para>
- This structure can be used to verify encryption levels, check server
- certificates, and more. Refer to the <productname>OpenSSL</productname>
- documentation for information about this structure.
+ For <productname>NSS</productname>, there is one struct available under
+ the name "NSS", and it returns a pointer to the
+ <productname>NSS</productname> <acronym>SSL</acronym>
+ <literal>PRFileDesc</literal> associated with the connection.
+ </para>
+ <para>
+ These structures can be used to verify encryption levels, check server
+ certificates, and more. Refer to the <acronym>SSL</acronym> library
+ documentation for information about these structures.
</para>
</listitem>
</varlistentry>
@@ -2660,6 +2829,10 @@ void *PQgetssl(const PGconn *conn);
<xref linkend="libpq-PQsslInUse"/> instead, and for more details about the
connection, use <xref linkend="libpq-PQsslAttribute"/>.
</para>
+ <para>
+ This function returns <literal>NULL</literal> when <acronym>SSL</acronym>
+ librariaes other than <productname>OpenSSL</productname> are used.
+ </para>
</listitem>
</varlistentry>
@@ -8716,6 +8889,12 @@ void PQinitOpenSSL(int do_ssl, int do_crypto);
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
@@ -8742,6 +8921,12 @@ void PQinitSSL(int do_ssl);
might be preferable for applications that need to work with older
versions of <application>libpq</application>.
</para>
+
+ <para>
+ This function does nothing when using <productname>NSS</productname> as
+ the <acronym>SSL</acronym> library, no special initialization is
+ required for <productname>NSS</productname>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index f77ed24204..132e8cbf80 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2182,15 +2182,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<indexterm zone="ssl-tcp">
<primary>SSL</primary>
+ <secondary>TLS</secondary>
</indexterm>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ a supported TLS library is installed on both client and
server systems and that support in <productname>PostgreSQL</productname> is
enabled at build time (see <xref linkend="installation"/>).
+ Supported libraries are <productname>OpenSSL</productname> and
+ <productname>NSS</productname>. The terms <acronym>SSL</acronym> and
+ <acronym>TLS</acronym> are often used interchangeably to mean a secure
+ connection using a <acronym>TLS</acronym> protocol, even though
+ <acronym>SSL</acronym> protocols are no longer supported.
</para>
<sect2 id="ssl-setup">
@@ -2210,8 +2216,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To start in <acronym>SSL</acronym> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</acronym> mode, a server certificate
+ and private key must exist. The below sections on the different libraries
+ will discuss how to configure these.
+ </para>
+
+ <para>
+ By default, these files are expected to be
named <filename>server.crt</filename> and <filename>server.key</filename>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file"/>
@@ -2301,6 +2312,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</note>
</sect2>
+ <sect2 id="ssl-nss-config">
+ <title>NSS Configuration</title>
+
+ <para>
+ <productname>PostgreSQL</productname> will look for certificates and keys
+ in the <productname>NSS</productname> database specified by the parameter
+ <xref linkend="guc-ssl-database"/> in <filename>postgresql.conf</filename>.
+ The parameters for certificate and key filenames are used to identify the
+ nicknames in the database.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-server-files">
- <title>SSL Server File Usage</title>
+ <title>SSL Server File Parameter Usage</title>
<para>
<xref linkend="ssl-file-usage"/> summarizes the files that are
@@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-database"/></entry>
+ <entry>certificate database</entry>
+ <entry>contains server certificates, keys and revocation lists; only
+ used when <productname>PostgreSQL</productname> is built with support
+ for <productname>NSS</productname>.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
@@ -2446,7 +2477,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</sect2>
<sect2 id="ssl-certificate-creation">
- <title>Creating Certificates</title>
+ <title>Creating Certificates with OpenSSL</title>
<para>
To create a simple self-signed certificate for the server, valid for 365
@@ -2550,6 +2581,89 @@ openssl x509 -req -in server.csr -text -days 365 \
</para>
</sect2>
+ <sect2 id="nss-certificate-database">
+ <title>NSS Certificate Databases</title>
+
+ <para>
+ When using <productname>NSS</productname>, all certificates and keys must
+ be loaded into an <productname>NSS</productname> certificate database.
+ </para>
+
+ <para>
+ To create a new <productname>NSS</productname> certificate database and
+ load the certificates created in <xref linkend="ssl-certificate-creation" />,
+ use the following <productname>NSS</productname> commands:
+<programlisting>
+certutil -d "sql:server.db" -N --empty-password
+certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C"
+certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C"
+</programlisting>
+ This will give the certificate the filename as the nickname identifier in
+ the database which is created as <filename>server.db</filename>.
+ </para>
+ <para>
+ Then load the server key, which requires converting it to
+ <acronym>PKCS#12</acronym> format using the
+ <productname>OpenSSL</productname> tools:
+<programlisting>
+openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \
+ -certfile root.crt -passout pass:
+pk12util -i server.pfx -d server.db -W ''
+</programlisting>
+ </para>
+ <para>
+ Finally a certificate revocation list can be loaded with the following
+ commands:
+<programlisting>
+crlutil -I -i server.crl -d server.db -B
+</programlisting>
+ </para>
+ </sect2>
+
+ <sect2 id="nss-ssl-certificate-creation">
+ <title>Creating Certificates with NSS</title>
+
+ <para>
+ To create a simple self-signed CA and certificate for the server, use the
+ following NSS commands. Replace
+ <replaceable>*.yourdomain.com</replaceable> with the server's host
+ name. Remove <replaceable>--empty-password</replaceable> in order to set
+ a password protecting the databases. The password can be passed to
+ <application>certutil</application> with the <literal>-f</literal> parameter.
+ First create a self-signed CA. To use a different certificate validity
+ time than the default, use <replaceable>-v</replaceable> to specify the
+ number of months of validity.
+<programlisting>
+mkdir root_ca.db
+certutil -N -d "sql:root_ca.db/" --empty-password
+certutil -S -d "sql:root_ca.db/" -n root_ca -s "CN=<replaceable>ca.yourdomain.com</replaceable>" \
+ -x -k rsa -g 2048 -m <replaceable>5432</replaceable> -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -Z SHA256
+certutil -L -d "sql:root_ca.db/" -n root_ca -a > root_ca.pem
+</programlisting>
+ Now create a server certificate database, and create a certificate signing
+ request for the CA. Then sign the request with the already created CA and
+ insert the certificate chain into the certificate database.
+<programlisting>
+mkdir server_cert.db
+certutil -N -d "sql:server_cert.db/" --empty-password
+certutil -R -d "sql:server_cert.db/" -s "CN=dbhost.yourdomain.com" \
+ -o server_cert.csr -g 2048 -Z SHA256
+
+certutil -C -d "sql:root_ca.db/" -c root_ca -i server_cert.csr \
+ -o server_cert.der -m <replaceable>5433</replaceable> \
+ --keyUsage keyEncipherment,dataEncipherment,digitalSignature \
+ --nsCertType sslServer -Z SHA256
+
+certutil -A -d "sql:server_cert.db/" -n root_ca -t CTu,CTu,CTu -a -i root_ca.pem
+certutil -A -d "sql:server_cert.db/" -n server_cert -t CTu,CTu,CTu -i server_cert.der
+</programlisting>
+ The server certificate is loaded into the certificate database as well as
+ the CA certificate in order to provide the chain of certificates.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="gssapi-enc">
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index d84a434a6e..c0722b558d 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -80,3 +80,31 @@ are unacceptable.
The downside to EDH is that it makes it impossible to use ssldump(1)
if there's a problem establishing an SSL session. In this case you'll
need to temporarily disable EDH (see initialize_dh()).
+
+
+Supporting a new TLS backend
+============================
+
+TLS support in PostgreSQL is implemented with an API which abstracts any
+knowledge about the library used from core server backend and frontend code.
+Adding support for a new TLS library can be done almost without changing any
+core PostgreSQL code. The modules which needs to be implemented are briefly
+described below.
+
+Each supported TLS library is implemented in its own file for the libpq
+frontend and backend. The API is defined in be-secure.c and fe-secure.c
+respectively. Each library can add relevant connection structures to the
+Port and PGConn objects.
+
+pg_cryptohash is an internal API for invoking cryptographic hash algorithms
+on data. Each supported TLS library implements the API defined in the header
+common/cryptohash.h, with the implementation used decided at build-time.
+
+pg_strong_random provide the backend with a source of cryptographically secure
+random numbers.
+
+contrib/sslinfo provide similar information to pg_stat_ssl, with a few library
+specific extensions.
+
+contrib/pgcrypto provide a larger selection of digest and hash algorithms
+when compiled against a TLS library.
--
2.24.3 (Apple Git-128)
v53-0004-test-check-for-empty-stderr-during-connect_ok.patchapplication/octet-stream; name=v53-0004-test-check-for-empty-stderr-during-connect_ok.patch; x-unix-mode=0644Download
From 0b1832cb6df5fd51333e67ee5240032755e903c0 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Sat, 19 Jun 2021 01:37:30 +0200
Subject: [PATCH v53 04/11] test: check for empty stderr during connect_ok()
...in a similar manner to command_like(), to catch notices-as-errors
coming from NSS.
---
src/test/authentication/t/001_password.pl | 2 +-
src/test/authentication/t/002_saslprep.pl | 2 +-
src/test/kerberos/t/001_auth.pl | 2 +-
src/test/ldap/t/001_auth.pl | 2 +-
src/test/perl/PostgreSQL/Test/Cluster.pm | 5 ++++-
src/test/ssl/t/001_ssltests.pl | 4 ++--
6 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 5d56455857..29110af614 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -20,7 +20,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 23;
+ plan tests => 32;
}
diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl
index d8995b1ae5..808ed8991b 100644
--- a/src/test/authentication/t/002_saslprep.pl
+++ b/src/test/authentication/t/002_saslprep.pl
@@ -17,7 +17,7 @@ if (!$use_unix_sockets)
}
else
{
- plan tests => 12;
+ plan tests => 20;
}
# Delete pg_hba.conf from the given node, add a new entry to it
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 2b539d2402..0c0b7459fb 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -23,7 +23,7 @@ use Time::HiRes qw(usleep);
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 44;
+ plan tests => 54;
}
else
{
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index cdc4e1e5c8..39d8010aee 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -9,7 +9,7 @@ use Test::More;
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 28;
+ plan tests => 40;
}
else
{
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 265f3ae657..ff7178c6f5 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -2189,8 +2189,11 @@ sub connect_ok
if (defined($params{expected_stdout}))
{
- like($stdout, $params{expected_stdout}, "$test_name: matches");
+ like($stdout, $params{expected_stdout}, "$test_name: stdout matches");
}
+
+ is($stderr, "", "$test_name: no stderr");
+
if (@log_like or @log_unlike)
{
my $log_contents = PostgreSQL::Test::Utils::slurp_file($self->logfile, $log_location);
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7dc1731e64..430ce242d6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -19,12 +19,12 @@ my $nss;
if ($ENV{with_ssl} eq 'openssl')
{
$openssl = 1;
- plan tests => 112;
+ plan tests => 144;
}
elsif ($ENV{with_ssl} eq 'nss')
{
$nss = 1;
- plan tests => 112;
+ plan tests => 138;
}
else
{
--
2.24.3 (Apple Git-128)
v53-0003-nss-Add-NSS-specific-tests.patchapplication/octet-stream; name=v53-0003-nss-Add-NSS-specific-tests.patch; x-unix-mode=0644Download
From db9befb049f8bc4d6c28937a2b47bf365fc368a7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:37 +0100
Subject: [PATCH v53 03/11] 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/Makefile | 2 +-
src/test/ssl/Makefile | 11 +-
src/test/ssl/sslfiles.mk | 4 +-
src/test/ssl/sslfiles_nss.mk | 268 +++++++++++++++
src/test/ssl/t/001_ssltests.pl | 458 ++++++++++++++++----------
src/test/ssl/t/002_scram.pl | 33 +-
src/test/ssl/t/003_sslinfo.pl | 10 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 66 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 24 +-
src/test/ssl/t/SSL/Server.pm | 59 ++--
10 files changed, 697 insertions(+), 238 deletions(-)
create mode 100644 src/test/ssl/sslfiles_nss.mk
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
diff --git a/src/test/Makefile b/src/test/Makefile
index 46275915ff..79246d59a4 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_ssl),openssl)
+ifneq (,$(filter $(with_ssl),openssl nss))
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 12b02eb422..b22cb51556 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -18,14 +18,21 @@ include $(top_builddir)/src/Makefile.global
export with_ssl
# The sslfiles targets are separated into their own file due to interactions
-# with settings in Makefile.global.
+# with settings in Makefile.global. There is no library guard on with_ssl for
+# the targets, as nssfiles rely on the openssl generated files and also the
+# openssl toolchain to some degree.
.PHONY: sslfiles sslfiles-clean
sslfiles sslfiles-clean:
- $(MAKE) -f $(srcdir)/sslfiles.mk $@
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles.mk $@
+
+.PHONY: nssfiles nssfiles-clean
+nssfiles nssfiles-clean:
+ MKDIR_P="$(MKDIR_P)" $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
clean distclean maintainer-clean:
rm -rf tmp_check
$(MAKE) -f $(srcdir)/sslfiles.mk $@
+ $(MAKE) -f $(srcdir)/sslfiles_nss.mk $@
# Doesn't depend on sslfiles because we don't rebuild them by default
check:
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 7ed3a30f5c..065e08a104 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -185,7 +185,7 @@ ssl/%.csr: ssl/%.key conf/%.config
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
- mkdir $@
+ $(MKDIR_P) $@
ssl/%-certindex:
touch $@
@@ -225,7 +225,7 @@ ssl/client-crldir: ssl/client.crl
crlhashfile = $(shell openssl crl -hash -noout -in $(1)).r0
ssl/%-crldir:
- mkdir -p $@
+ $(MKDIR_P) $@
rm -f $@/*.r0
$(foreach crl,$^,cp $(crl) $@/$(call crlhashfile,$(crl)) &&) true
touch $@
diff --git a/src/test/ssl/sslfiles_nss.mk b/src/test/ssl/sslfiles_nss.mk
new file mode 100644
index 0000000000..adb7363d63
--- /dev/null
+++ b/src/test/ssl/sslfiles_nss.mk
@@ -0,0 +1,268 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for sslfiles using NSS
+#
+# The SSL test files are completely disjoint from the rest of the build; they
+# don't rely on other targets or on Makefile.global. The targets in this
+# file rely on the certificates and keys generated by the OpenSSL backend
+# support.
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/ssl/sslfiles_nss.mk
+#
+#-------------------------------------------------------------------------
+
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimic the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crldir.db \
+ ssl/nss/native_ca-root.db \
+ ssl/nss/native_server-root.db \
+ ssl/nss/native_client-root.db
+
+nssfiles: $(NSSFILES)
+
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crldir.db: ssl/root+server_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ crlutil -I -i ssl/nss/root.crl -d $@ -B
+ for c in $(shell ls ssl/root+server-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+server-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+# pk12util won't preserve the password when importing the password protected
+# key, the password must be set on the database *before* importing it as the
+# password in the pkcs12 envelope will be dropped.
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ echo "secret1" > password.txt
+ certutil -d "sql:$@" -N -f password.txt
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1'
+ pk12util -i ssl/nss/server-password.pfx -d "sql:$@" -W 'secret1' -K 'secret1'
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-only.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-only.crt__server-cn-only.key.crldir.db: ssl/nss/server-cn-only.crt__server-cn-only.key.db
+ cp -R $< $@
+ for c in $(shell ls ssl/root+client-crldir) ; do \
+ echo $${c} ; \
+ openssl crl -in ssl/root+client-crldir/$${c} -outform der -out ssl/nss/$${c} ; \
+ crlutil -I -i ssl/nss/$${c} -d $@ -B ; \
+ done
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-multiple-alt-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass:
+ pk12util -i ssl/nss/server-single-alt-name.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass:
+ pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass:
+ pk12util -i ssl/nss/server-no-names.pfx -d "sql:$@" -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass:
+ pk12util -i ssl/nss/server-revoked.pfx -d "sql:$@" -W ''
+
+
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+'
+ pk12util -i ssl/nss/client-encrypted-pem.pfx -d "sql:$@" -W 'dUmmyP^#+' -k $@.pass
+
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client-revoked.pfx -d "sql:$@" -W ''
+
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass:
+ pk12util -i ssl/nss/client.pfx -d "sql:$@" -W ''
+
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
+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 $@
+
+#### NSS specific certificates and keys
+
+ssl/nss/native_ca-%.db:
+ $(MKDIR_P) ssl/nss/native_ca-$*.db
+ certutil -N -d "sql:ssl/nss/native_ca-$*.db/" --empty-password
+ echo y > nss_ca_params.txt
+ echo 10 >> nss_ca_params.txt
+ echo y >> nss_ca_params.txt
+ cat nss_ca_params.txt | certutil -S -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* \
+ -s "CN=Test CA for PostgreSQL SSL regression tests,OU=PostgreSQL test suite" \
+ -x -k rsa -g 2048 -m 5432 -t CTu,CTu,CTu \
+ --keyUsage certSigning -2 --nsCertType sslCA,smimeCA,objectSigningCA \
+ -z Makefile -Z SHA256
+ rm nss_ca_params.txt
+
+ssl/nss/native_ca-%.pem: ssl/nss/native_ca-%.db
+ certutil -L -d "sql:ssl/nss/native_ca-$*.db/" -n ca-$* -a > ssl/nss/native_ca-$*.pem
+
+# Create and sign a server certificate
+ssl/nss/native_server-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_server-$*.db
+ certutil -N -d "sql:ssl/nss/native_server-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_server-$*.db/" \
+ -s "CN=common-name.pg-ssltest.test,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_server-$*.csr -g 2048 -Z SHA256 -z Makefile
+ echo 1 > nss_server_params.txt
+ echo 9 >> nss_server_params.txt
+ cat nss_server_params.txt | certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-root -i ssl/nss/native_server-$*.csr \
+ -o ssl/nss/native_server_$*.der -m 5433 --keyUsage dataEncipherment,digitalSignature,keyEncipherment \
+ --nsCertType sslServer --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_server-$*.db/" -n ssl/native_server-$*.crt -t CTu,CTu,CTu -i ssl/nss/native_server_$*.der
+ rm nss_server_params.txt
+
+# Create and sign a client certificate
+ssl/nss/native_client-%.db: ssl/nss/native_ca-%.pem
+ $(MKDIR_P) ssl/nss/native_client-$*.db
+ certutil -N -d "sql:ssl/nss/native_client-$*.db/" --empty-password
+ certutil -R -d "sql:ssl/nss/native_client-$*.db/" -s "CN=ssltestuser,OU=PostgreSQL test suite" \
+ -o ssl/nss/native_client-$*.csr -g 2048 -Z SHA256 -z Makefile
+ certutil -C -d "sql:ssl/nss/native_ca-$*.db/" -c ca-$* -i ssl/nss/native_client-$*.csr -o ssl/nss/native_client-$*.der \
+ -m 5434 --keyUsage keyEncipherment,dataEncipherment,digitalSignature --nsCertType sslClient \
+ --certVersion 1 -Z SHA256
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n ca-$* -t CTu,CTu,CTu -a -i ssl/nss/native_ca-$*.pem
+ certutil -A -d "sql:ssl/nss/native_client-$*.db" -n native_client-$* -t CTu,CTu,CTu -i ssl/nss/native_client-$*.der
+
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
+# The difference between the below clean targets and nssfiles-clean is that the
+# clean targets will be run during a "standard" recursive clean run from the
+# main build tree. The nssfiles-clean target must be run explicitly from this
+# directory.
+.PHONY: clean distclean maintainer-clean
+clean distclean maintainer-clean:
+ rm -rf ssl/nss
+
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 8d6b9dfbdc..7dc1731e64 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,13 +13,22 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ plan tests => 112;
+}
+elsif ($ENV{with_ssl} eq 'nss')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ $nss = 1;
+ plan tests => 112;
}
else
{
- plan tests => 110;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
@@ -55,19 +64,68 @@ configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
note "testing password-protected keys";
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo wrongpassword');
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
+# Since the passphrase callbacks operate at different stages in OpenSSL and
+# NSS we have two separate blocks for them
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 2
+ if ($nss);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword',
+ restart => 'no' );
+
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1',
+ restart => 'no');
+
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file');
+ $node->_update_pid(1);
+}
-set_server_cert($node, 'server-cn-only', 'root+client_ca',
- 'server-password', 'echo secret1');
-command_ok(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart succeeds with password-protected key file');
-$node->_update_pid(1);
+SKIP:
+{
+ skip "Certificate passphrases are checked on connection in NSS", 3
+ if ($openssl);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo wrongpassword');
+
+ $node->connect_fails(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with incorrect key password configured",
+ expected_stderr => qr/\QSSL error\E/);
+
+ switch_server_cert($node,
+ certfile => 'server-cn-only',
+ cafile => 'root+client_ca',
+ keyfile => 'server-password',
+ nssdatabase => 'server-cn-only.crt__server-password.key.db',
+ passphrase_cmd => 'echo secret1');
+
+ $node->connect_ok(
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test sslrootcert=invalid sslmode=require",
+ "connect to server with correct key password configured");
+}
# Test compatibility of SSL protocols.
# TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -95,7 +153,7 @@ command_ok(
note "running client tests";
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -114,87 +172,104 @@ $node->connect_ok(
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-ca",
"connect without server root cert sslmode=verify-ca",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expected_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
"$common_connstr sslrootcert=invalid sslmode=verify-full",
"connect without server root cert sslmode=verify-full",
- expected_stderr => qr/root certificate file "invalid" does not exist/);
+ expcted_stderr => qr/root certificate file "invalid" does not exist|Peer's certificate issuer has been marked as not trusted by the user/);
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=require",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-ca",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full ssldatabase=ssl/nss/client_ca.crt.db",
"connect with wrong server root cert sslmode=verify-full",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|Peer's certificate issuer has been marked as not trusted by the user/);
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- "connect with server CA cert, without root CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesn't work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# And finally, with the correct root cert.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=require");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-ca");
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db",
"connect with correct server CA cert file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-$node->connect_ok(
- "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
-
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+
+ # How about import the both-file into a database?
+ $node->connect_ok(
+ "$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid ssldatabase=ssl/nss/root+server_ca.crt.db",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- "CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 4 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
-# The same for CRL directory
-$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
- "directory CRL belonging to a different CA",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ # The same for CRL directory
+ $node->connect_fails(
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+ "directory CRL belonging to a different CA",
+ expected_stderr => qr/SSL error: certificate verify failed/);
+}
# With the correct CRL, succeeds (this cert is not revoked)
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"CRL with a non-revoked cert");
# The same for CRL directory
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"directory CRL with a non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
"mismatch between host name and server certificate sslmode=require");
@@ -205,14 +280,14 @@ $node->connect_fails(
"$common_connstr sslmode=verify-full host=wronghost.test",
"mismatch between host name and server certificate sslmode=verify-full",
expected_stderr =>
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/
+ qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|requested domain name does not match the server's certificate/
);
# Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names', nssdatabase => 'server-multiple-alt-names.crt__server-multiple-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=dns1.alt-name.pg-ssltest.test",
@@ -227,21 +302,22 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
+
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with X.509 Subject Alternative Names wildcard",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name', nssdatabase => 'server-single-alt-name.crt__server-single-alt-name.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr host=single.alt-name.pg-ssltest.test",
@@ -251,21 +327,21 @@ $node->connect_fails(
"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
$node->connect_fails(
"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
"host name not matching with a single X.509 Subject Alternative Name wildcard",
expected_stderr =>
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|requested domain name does not match the server's certificate/,
);
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names', nssdatabase => 'server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
"certificate with both a CN and SANs 1");
@@ -275,43 +351,43 @@ $node->connect_fails(
"$common_connstr host=common-name.pg-ssltest.test",
"certificate with both a CN and SANs ignores CN",
expected_stderr =>
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/
+ qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|requested domain name does not match the server's certificate/
);
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names', nssdatabase => 'server-no-names.crt__server-no-names.key.db');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/root+server_ca.crt.db";
$node->connect_ok(
"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-ca");
$node->connect_fails(
$common_connstr . " "
- . "sslmode=verify-full host=common-name.pg-ssltest.test",
+ . "sslmode=verify-full host=common-name.pg-ssltest.test",
"server certificate without CN or SANs sslmode=verify-full",
expected_stderr =>
- qr/could not get server's host name from server certificate/);
+ qr/could not get server's host name from server certificate|requested domain name does not match the server's certificate./);
# Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
$common_connstr =
"user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
# Without the CRL, succeeds. With it, fails.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca ssldatabase=ssl/nss/root+server_ca.crt.db",
"connects without client-side CRL");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl ssldatabase=ssl/nss/root+server_ca.crt__root+server.crl.db",
"does not connect with client-side CRL file",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir ssldatabase=ssl/nss/root+server_ca.crt__root+server.crldir.db",
"does not connect with client-side CRL directory",
- expected_stderr => qr/SSL error: certificate verify failed/);
+ expected_stderr => qr/SSL error: certificate verify failed|SSL error: Peer's Certificate has been revoked/);
# pg_stat_ssl
command_like(
@@ -329,29 +405,46 @@ command_like(
# Test min/max SSL protocol versions.
$node->connect_ok(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection success with correct range of TLS protocol versions");
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with incorrect range of TLS protocol versions",
expected_stderr => qr/invalid SSL protocol version range/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol minimum bound",
expected_stderr => qr/invalid ssl_min_protocol_version value/);
$node->connect_fails(
- "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+ "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls ssldatabase=ssl/nss/root+server_ca.crt.db",
"connection failure with an incorrect SSL protocol maximum bound",
expected_stderr => qr/invalid ssl_max_protocol_version value/);
+# tests of NSS generated certificates/keys
+SKIP:
+{
+ skip "NSS specific tests", 1 if ($openssl);
+
+ switch_server_cert($node, certfile => 'native_server-root', cafile => 'native_ca-root', nssdatabase => 'native_server-root.db');
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+
+ $node->connect_ok(
+ "$common_connstr sslmode=require user=ssltestuser",
+ "NSS generated certificates"
+ );
+}
+
### Server-side tests.
###
### Test certificate authorization.
+switch_server_cert($node, certfile => 'server-revoked', nssdatabase => 'server-revoked.crt__server-revoked.key.db');
+
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
# no client cert
$node->connect_fails(
@@ -361,66 +454,86 @@ $node->connect_fails(
# correct client cert in unencrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization succeeds with correct client cert in PEM format"
);
-# correct client cert in unencrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-der.key'}",
- "certificate authorization succeeds with correct client cert in DER format"
-);
+SKIP:
+{
+ skip "Automatic certificate resolution is NSS specific", 1 if ($openssl);
+ # correct client cert in unencrypted PEM without a nickname (sslcert)
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser",
+ "certificate authorization succeeds without correct client cert in connstring"
+ );
+}
+
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in unencrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-der.key'),
+ "certificate authorization succeeds with correct client cert in DER format"
+ );
+}
# correct client cert in encrypted PEM
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='dUmmyP^#+'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='dUmmyP^#+' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization succeeds with correct client cert in encrypted PEM format"
);
-# correct client cert in encrypted DER
-$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-der.key'} sslpassword='dUmmyP^#+'",
- "certificate authorization succeeds with correct client cert in encrypted DER format"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # correct client cert in encrypted DER
+ $node->connect_ok(
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-der.key') . " sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format"
+ );
+}
# correct client cert in encrypted PEM with wrong password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='wrong'",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='wrong' ssldatabase=ssl/nss/client.crt__client-encrypted-pem.key.db",
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": bad decrypt\E!
+ qr!connection requires a valid client certificate|private key file \".*/client-encrypted-pem\.key\": bad decrypt!,
);
-
-# correct client cert using whole DN
-my $dn_connstr = "$common_connstr dbname=certdb_dn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN mapping",
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-# same thing but with a regex
-$dn_connstr = "$common_connstr dbname=certdb_dn_re";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with DN regex mapping");
-
-# same thing but using explicit CN
-$dn_connstr = "$common_connstr dbname=certdb_cn";
-
-$node->connect_ok(
- "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
- "certificate authorization succeeds with CN mapping",
- # the full DN should still be used as the authenticated identity
- log_like => [
- qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
- ],);
-
-
+SKIP:
+{
+ skip "DN mapping not implemented in NSS", 3 if ($nss);
+
+ # correct client cert using whole DN
+ my $dn_connstr = "$common_connstr dbname=certdb_dn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but with a regex
+ $dn_connstr = "$common_connstr dbname=certdb_dn_re";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
+
+ # same thing but using explicit CN
+ $dn_connstr = "$common_connstr dbname=certdb_cn";
+
+ $node->connect_ok(
+ "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
+ "certificate authorization succeeds with CN mapping");
+}
TODO:
{
@@ -431,49 +544,52 @@ TODO:
# correct client cert in encrypted PEM with empty password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword=''",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword=''",
"certificate authorization fails with correct client cert and empty password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
# correct client cert in encrypted PEM with no password
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key'),
"certificate authorization fails with correct client cert and no password in encrypted PEM format",
expected_stderr =>
- qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
+ qr!\Qprivate key file ".*client-encrypted-pem.key": processing error\E!
);
}
# pg_stat_ssl
-my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
-if ($? == 0)
+# If the serial number can't be extracted to match against, fall back to just
+# checking for it being integer.
+# TODO: figure put a corresponding command for NSS to extract the serial from
+# the cert.
+my $serialno = '\d+';
+if ($openssl)
{
- # OpenSSL prints serial numbers in hexadecimal and converting the serial
- # from hex requires a 64-bit capable Perl as the serialnumber is based on
- # the current timestamp. On 32-bit fall back to checking for it being an
- # integer like how we do when grabbing the serial fails.
- if ($Config{ivsize} == 8)
+ my $serialno = `openssl x509 -serial -noout -in ssl/client.crt`;
+ if ($? == 0)
{
- $serialno =~ s/^serial=//;
- $serialno =~ s/\s+//g;
- $serialno = hex($serialno);
+ # OpenSSL prints serial numbers in hexadecimal and converting the serial
+ # from hex requires a 64-bit capable Perl as the serialnumber is based on
+ # the current timestamp. On 32-bit fall back to checking for it being an
+ # integer like how we do when grabbing the serial fails.
+ if ($Config{ivsize} == 8)
+ {
+ $serialno =~ s/^serial=//;
+ $serialno =~ s/\s+//g;
+ $serialno = hex($serialno);
+ }
}
else
{
- $serialno = '\d+';
+ # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
+ # skipping the test over, so just fall back to a generic integer match.
+ warn 'couldn\'t run `openssl x509` to get client cert serialno';
}
}
-else
-{
- # OpenSSL isn't functioning on the user's PATH. This probably isn't worth
- # skipping the test over, so just fall back to a generic integer match.
- warn 'couldn\'t run `openssl x509` to get client cert serialno';
- $serialno = '\d+';
-}
command_like(
[
@@ -485,42 +601,47 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key') . " ssldatabase=ssl/nss/client.crt__client.key.db",
'-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client_wrongperms.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client_wrongperms.key'),
"certificate authorization fails because of file permissions",
expected_stderr =>
- qr!\Qprivate key file "$key{'client_wrongperms.key'}" has group or world access\E!
+ qr!private key file \".*client_wrongperms\.key\" has group or world access!
);
}
# client cert belonging to another user
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/,
# certificate authentication should be logged even on failure
log_like =>
[qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db";
+
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL error: Peer's certificate issuer has been marked as not trusted by the user/,
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -528,16 +649,16 @@ $node->connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db";
$node->connect_ok(
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full succeeds with matching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
$node->connect_fails(
- "$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
expected_stderr =>
qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
@@ -547,15 +668,15 @@ $node->connect_fails(
# Check that connecting with auth-optionverify-ca in pg_hba :
# works, when username doesn't match Common Name
$node->connect_ok(
- "$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
+ "$common_connstr user=yetanotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
# verify-full does not provide authentication
log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=$key{'client.key'} sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
+ "user=ssltestuser dbname=certdb " . sslkey('client.key') . " sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client+client_ca.crt__client.key.db";
$node->connect_ok(
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
@@ -563,14 +684,15 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr =>
+ qr/SSL error: tlsv1 alert unknown ca|connection requires a valid client certificate/);
# test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef,
- 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir', nssdatabase => 'server-cn-only.crt__server-cn-only.key.crldir.db');
# revoked client cert
$node->connect_fails(
- "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key') . " ssldatabase=ssl/nss/client-revoked.crt__client-revoked.key.db",
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr =>
+ qr/SSL error: sslv3 alert certificate revoked|SSL peer rejected your certificate as revoked/);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 56c5f88d5f..fd3c881040 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -16,7 +16,24 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+my $openssl;
+my $nss;
+
+my $supports_tls_server_end_point;
+
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $openssl = 1;
+ # Determine whether build supports tls-server-end-point.
+ $supports_tls_server_end_point =
+ check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $nss = 1;
+ $supports_tls_server_end_point = 1;
+}
+else
{
plan skip_all => 'SSL not supported by this build';
}
@@ -26,12 +43,6 @@ my $SERVERHOSTADDR = '127.0.0.1';
# This is the pattern to use in pg_hba.conf to match incoming connections.
my $SERVERHOSTCIDR = '127.0.0.1/32';
-# Determine whether build supports tls-server-end-point.
-my $supports_tls_server_end_point =
- check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-
-my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
-
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -50,7 +61,7 @@ $node->start;
# Configure server for SSL connections, with password handling.
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
"scram-sha-256", 'password' => "pass", 'password_enc' => "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only', nssdatabase => 'server-cn-only.crt__server-cn-only.key.db');
$ENV{PGPASSWORD} = "pass";
$common_connstr =
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -104,7 +115,7 @@ chmod 0600, "$cert_tempdir/client_scram.key"
or die "failed to change permissions on $cert_tempdir/client_scram.key: $!";
$client_tmp_key =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
$node->connect_fails(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=certdb user=ssltestuser channel_binding=require",
"Cert authentication and channel_binding=require",
expected_stderr =>
qr/channel binding required, but server authenticated client without channel binding/
@@ -112,10 +123,10 @@ $node->connect_fails(
# Certificate verification at the connection level should still work fine.
$node->connect_ok(
- "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser",
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR ssldatabase=ssl/nss/client.crt__client.key.db dbname=verifydb user=ssltestuser",
"SCRAM with clientcert=verify-full",
log_like => [
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
]);
-done_testing($number_of_tests);
+done_testing();
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 6c733c8a75..4dc457e1b6 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -14,13 +14,17 @@ use lib $FindBin::RealBin;
use SSL::Server;
-if ($ENV{with_ssl} ne 'openssl')
+if ($ENV{with_ssl} eq 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan tests => 13;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ plan skip_all => 'SSL not supported by this build';
}
else
{
- plan tests => 13;
+ plan skip_all => 'SSL not supported by this build';
}
#### Some configuration
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..e2b9b8cfc9
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,66 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend get_nss_key);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub get_nss_key
+{
+ return " ";
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+
+ my $sslconf =
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='ssl/$params->{certfile}.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$params->{nssdatabase}'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 991953bf2c..7caa8f23ee 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -72,26 +72,28 @@ sub init
sub get_openssl_key
{
- my $self = $_[0];
- my $keyfile = $_[1];
+ my $keyfile = shift;
- return $key{$keyfile};
+ return " sslkey=$key{$keyfile}";
}
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub set_server_cert
{
- my $self = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || $certfile;
+ my $self = $_[0];
+ my $params = $_[1];
+
+ $params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+ $params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+ $params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
my $sslconf =
- "ssl_ca_file='$cafile.crt'\n"
- . "ssl_cert_file='$certfile.crt'\n"
- . "ssl_key_file='$keyfile.key'\n"
- . "ssl_crl_file='root+client.crl'\n";
+ "ssl_ca_file='$params->{cafile}.crt'\n"
+ . "ssl_cert_file='$params->{certfile}.crt'\n"
+ . "ssl_key_file='$params->{keyfile}.key'\n"
+ . "ssl_crl_file='$params->{crlfile}'\n";
+ $sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
return $sslconf;
}
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 9275876250..ef7b17ed74 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -37,7 +37,7 @@ use File::Basename;
use File::Copy;
use Test::More;
use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
-use SSL::Backend::NSS qw(get_new_nss_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend get_nss_key);
our ($openssl, $nss, $backend);
@@ -58,9 +58,8 @@ elsif ($ENV{with_ssl} eq 'nss')
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
- set_server_cert
switch_server_cert
- key
+ sslkey
);
# Copy a set of files, taking into account wildcards
@@ -79,11 +78,12 @@ sub copy_files
return;
}
-sub key
+# Return a sslkey construct for use in a connection string.
+sub sslkey
{
- my ($node, $keyfile) = @_;
+ my $keyfile = shift;
- return get_openssl_key($keyfile) if (defined($openssl);
+ return get_openssl_key($keyfile) if (defined($openssl));
return get_nss_key($keyfile) if (defined($nss));
}
@@ -168,7 +168,7 @@ sub configure_test_server_for_ssl
}
elsif (defined($nss))
{
- RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ PostgreSQL::Test::RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
}
# Stop and restart server to load new listen_addresses.
@@ -193,46 +193,25 @@ sub cleanup
$backend->cleanup();
}
-# Change the configuration to use given server cert file,
-sub set_server_cert
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect. Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
+sub switch_server_cert
{
- my $node = $_[0];
- my $certfile = $_[1];
- my $cafile = $_[2] || "root+client_ca";
- my $keyfile = $_[3] || '';
- my $pwcmd = $_[4] || '';
- my $crlfile = "root+client.crl";
- my $crldir;
+ my $node = shift;
+ my %params = @_;
my $pgdata = $node->data_dir;
- $keyfile = $certfile if $keyfile eq '';
-
- # defaults to use crl file
- if (defined $_[3] || defined $_[4])
- {
- $crlfile = $_[3];
- $crldir = $_[4];
- }
-
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
- print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
- print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
- print $sslconf "ssl_passphrase_command='$pwcmd'\n"
- unless $pwcmd eq '';
+ print $sslconf $backend->set_server_cert(\%params);
+ print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+ if defined $params{passphrase_cmd};
close $sslconf;
- return;
-}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-# Takes the same arguments as set_server_cert, which it calls to do that
-# piece of the work.
-sub switch_server_cert
-{
- my $node = $_[0];
- set_server_cert(@_);
+ return if (defined($params{restart}) && $params{restart} eq 'no');
+
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v53-0002-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v53-0002-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From 5735872edf3f6d3e12f09c4c61c3d8511c33ef82 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v53 02/11] Refactor SSL testharness for multiple library
The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.
No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
src/test/ssl/t/001_ssltests.pl | 57 +--------
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/003_sslinfo.pl | 2 +-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 110 ++++++++++++++++++
.../ssl/t/{SSLServer.pm => SSL/Server.pm} | 92 ++++++++++++---
5 files changed, 196 insertions(+), 69 deletions(-)
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (77%)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b1fb15ce80..8d6b9dfbdc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -8,12 +8,10 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
@@ -36,39 +34,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes to using keys stored in a temporary path for the rest of
-# the tests. To get the full path for inclusion in connection strings, the
-# %key hash can be interrogated.
-my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
-my %key;
-my @keys = (
- "client.key", "client-revoked.key",
- "client-der.key", "client-encrypted-pem.key",
- "client-encrypted-der.key", "client-dn.key");
-foreach my $keyfile (@keys)
-{
- copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
- or die
- "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
- chmod 0600, "$cert_tempdir/$keyfile"
- or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
- $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
- $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
-}
-
-# Also make a copy of that explicitly world-readable. We can't
-# necessarily rely on the file in the source tree having those
-# permissions.
-copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
- or die
- "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
-chmod 0644, "$cert_tempdir/client_wrongperms.key"
- or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
-$key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
-$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
#### Set up the server.
note "setting up data directory";
@@ -83,32 +48,22 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
command_fails(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart fails with password-protected key file with wrong password');
$node->_update_pid(0);
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 86312be88c..56c5f88d5f 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -14,11 +14,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
- plan skip_all => 'OpenSSL not supported by this build';
+ plan skip_all => 'SSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/003_sslinfo.pl b/src/test/ssl/t/003_sslinfo.pl
index 8c760b39db..6c733c8a75 100644
--- a/src/test/ssl/t/003_sslinfo.pl
+++ b/src/test/ssl/t/003_sslinfo.pl
@@ -12,7 +12,7 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_ssl} ne 'openssl')
{
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..991953bf2c
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,110 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend get_openssl_key);
+
+our (%key);
+
+INIT
+{
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # The client's private key must not be world-readable, so take a copy
+ # of the key stored in the code tree and update its permissions.
+ #
+ # This changes to using keys stored in a temporary path for the rest of
+ # the tests. To get the full path for inclusion in connection strings, the
+ # %key hash can be interrogated.
+ my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
+ my @keys = (
+ "client.key", "client-revoked.key",
+ "client-der.key", "client-encrypted-pem.key",
+ "client-encrypted-der.key", "client-dn.key");
+ foreach my $keyfile (@keys)
+ {
+ copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
+ or die
+ "couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
+ chmod 0600, "$cert_tempdir/$keyfile"
+ or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
+ $key{$keyfile} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/$keyfile");
+ $key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+ }
+
+ # Also make a copy of that explicitly world-readable. We can't
+ # necessarily rely on the file in the source tree having those
+ # permissions.
+ copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
+ or die
+ "couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
+ chmod 0644, "$cert_tempdir/client_wrongperms.key"
+ or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
+ $key{'client_wrongperms.key'} = PostgreSQL::Test::Utils::perl2host("$cert_tempdir/client_wrongperms.key");
+ $key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+}
+
+sub get_openssl_key
+{
+ my $self = $_[0];
+ my $keyfile = $_[1];
+
+ return $key{$keyfile};
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 77%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index c85c6fd997..9275876250 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -26,20 +26,41 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::RecursiveCopy;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend get_openssl_key);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_ssl} eq 'nss')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
configure_test_server_for_ssl
+ set_server_cert
switch_server_cert
+ key
);
# Copy a set of files, taking into account wildcards
@@ -58,6 +79,14 @@ sub copy_files
return;
}
+sub key
+{
+ my ($node, $keyfile) = @_;
+
+ return get_openssl_key($keyfile) if (defined($openssl);
+ return get_nss_key($keyfile) if (defined($nss));
+}
+
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
sub configure_test_server_for_ssl
@@ -126,14 +155,21 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
- mkdir("$pgdata/root+client-crldir");
- copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ mkdir("$pgdata/root+client-crldir");
+ copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -141,20 +177,36 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
{
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $crlfile = "root+client.crl";
my $crldir;
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
# defaults to use crl file
if (defined $_[3] || defined $_[4])
{
@@ -164,13 +216,23 @@ sub switch_server_cert
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
+ return;
+}
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+ my $node = $_[0];
+ set_server_cert(@_);
$node->restart;
return;
}
--
2.24.3 (Apple Git-128)
v53-0001-nss-Support-libnss-as-TLS-library-in-libpq.patchapplication/octet-stream; name=v53-0001-nss-Support-libnss-as-TLS-library-in-libpq.patch; x-unix-mode=0644Download
From 94a6516c7e01e82a518984d1e0aa8832d915b411 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:22 +0100
Subject: [PATCH v53 01/11] nss: Support libnss as TLS library in libpq
This commit contains the frontend and backend portion of TLS support
in libpq to allow encrypted connections using NSS. The implementation
is done as a drop-in replacement for the OpenSSL support and leverages
the same internal API for abstracting library specific details.
A new GUC, ssl_database, is used to identify the NSS database used for
the serverside certificates and keys. The existing certificate and key
GUCs are used for providing certificate and key nicknames.
Client side there is a new connection parameter, cert_database, to
identify the client cert and key. All existing sslmodes are supported
in the same way as with OpenSSL.
Authors: Daniel Gustafsson, Andrew Dunstan, Jacob Champion
Reviewed-by: Michael Paquier
---
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/backend/libpq/auth.c | 6 +
src/backend/libpq/be-secure-nss.c | 1553 +++++++++++++++++
src/backend/libpq/be-secure.c | 1 +
src/backend/utils/misc/guc.c | 18 +-
src/common/cipher_nss.c | 192 ++
src/common/protocol_nss.c | 59 +
src/include/common/pg_nss.h | 52 +
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1170 +++++++++++++
src/interfaces/libpq/fe-secure.c | 21 +
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 31 +-
16 files changed, 3131 insertions(+), 7 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/common/cipher_nss.c
create mode 100644 src/common/protocol_nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 7d6f7d9e3d..8582d9f98b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -9509,7 +9509,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, ssldatabase, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, truncatable, fetch_size, batch_size, async_capable, keep_connections
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f3135..eda595624f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2754,7 +2754,13 @@ CheckCertAuth(Port *port)
int status_check_usermap = STATUS_ERROR;
char *peer_username = NULL;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* select the correct field to compare */
switch (port->hba->clientcertname)
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..79f808ce7e
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1553 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "common/pg_nss.h"
+
+#include <base64.h>
+#include <cert.h>
+#include <certdb.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secder.h>
+#include <secerr.h>
+#include <secitem.h>
+#include <secport.h>
+#include <ssl.h>
+#include <sslerr.h>
+
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext *nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRStatus pg_ssl_close(PRFileDesc *fd);
+/* Utility functions */
+static PRFileDesc *init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static char *pg_CERT_GetDistinguishedName(CERTCertificate *cert);
+static const char *tag_to_name(SECOidTag tag);
+static void pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert);
+static bool pg_configure_ssl_ciphers(PRFileDesc *model);
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum. Error
+ * messages should be kept identical to those in be-secure-openssl.c to
+ * make translations easier.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version);
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+/*
+ * be_tls_open_server
+ *
+ * NSPR an NSS initialization must happen after forking, so most of the actual
+ * setup of NSPR/NSS is done here rather than in be_tls_init. NSS is documented
+ * as not being fork safe even for a read-only implementation. This introduce
+ * differences with the OpenSSL support which performs initialization during
+ * postmaster startup and can thus report errors earlier. Improving this for
+ * the NSS backend is a TODO.
+ */
+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;
+
+ port->peer_cert_valid = false;
+ port->ssl_in_use = false;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we won't allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶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 = PR_ImportTCPSocket(port->sock);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to connect to socket")));
+ return -1;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here as well in that we
+ * can error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to open socket")));
+ return -1;
+ }
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to enable TLS on socket")));
+ return -1;
+ }
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection")));
+ return -1;
+ }
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure TLS connection as server")));
+ return -1;
+ }
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the preferred ciphers. An appropriate ereport message will
+ * have been set in case there is an error, so simply returning is fine
+ * here.
+ */
+ if (!pg_configure_ssl_ciphers(model))
+ return -1;
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+ return -1;
+ }
+
+ /*
+ * Set up the custom IO layer in order to be able to provide custom call-
+ * backs for IO operations which override the built-in behavior. For now
+ * we need this in order to support debug builds of NSS/NSPR.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ return -1;
+
+ if (PR_PushIOLayer(port->pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(COMMERROR,
+ (errmsg("unable to push IO layer")));
+ return -1;
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ return -1;
+ }
+ else
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ {
+ ereport(COMMERROR,
+ (errmsg("specified CRL not found in database")));
+ return -1;
+ }
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ /* Set up certificate authentication callback */
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ /*
+ * Apply the configuration from the model template onto our actual socket
+ * to set it up as a TLS server.
+ */
+ port->pr_fd = SSL_ImportFD(model, port->pr_fd);
+ if (!port->pr_fd)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to configure socket for TLS server mode: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ /*
+ * The intention with the model FD is to keep it as a template for coming
+ * connections to amortize the cost and complexity across all client
+ * sockets. Since we won't get another socket connected in this backend
+ * we can however close the model immediately.
+ */
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ {
+ /*
+ * NSS returns PR_END_OF_FILE_ERROR if the handshake was incomplete,
+ * which for example will happen if the client hasn't been set up with
+ * the correct certificates etc.
+ */
+ if (PR_GetError() == PR_END_OF_FILE_ERROR)
+ ereport(COMMERROR,
+ (errmsg("could not accept SSL connection: EOF detected")));
+ else
+ ereport(COMMERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ port->ssl_in_use = true;
+
+ /* Register callback for TLS alerts */
+ SSL_AlertSentCallback(port->pr_fd, pg_SSLAlertSent, port);
+
+ return 0;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+/*
+ * be_tls_close
+ *
+ * Callback for closing down the current connection, if any.
+ */
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ /*
+ * Immediately signal to the rest of the backend that this connnection is
+ * no longer to be considered to be using TLS encryption.
+ */
+ port->ssl_in_use = false;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ if (port->pr_fd)
+ {
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ }
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+/*
+ * be_tls_destroy
+ *
+ * Callback for destroying global contexts during SIGHUP.
+ */
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+/*
+ * be_tls_get_cipher_bits
+ *
+ * Returns the number of bits in the encryption key used, or zero in case
+ * of errors.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+/*
+ * be_tls_get_cipher
+ *
+ * Returns the cipher used for the current connection, or NULL in case of
+ * errors.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+/*
+ * be_tls_get_peer_subject_name
+ *
+ * Returns the subject name of the peer certificate.
+ */
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_issuer_name
+ *
+ * Returns the issuer name of the peer certificate.
+ */
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_peer_serial
+ *
+ * Returns the serial of the peer certificate.
+ */
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+/*
+ * be_tls_get_certificate_hash
+ *
+ * Returns the hash data of the server certificate for SCRAM channel binding.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+ PK11Context *ctx = NULL;
+ unsigned int outlen;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_tag = SECOID_GetAlgorithmTag(&certificate->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ elog(ERROR, "could not find digest for OID '%s'",
+ SECOID_FindOIDTagDescription(signature_tag));
+
+ /*
+ * The TLS server's certificate bytes need to be hashed with SHA-256 if
+ * its signature algorithm is MD5 or SHA-1 as per RFC 5929
+ * (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
+ * is used, the same hash as the signature algorithm is used.
+ */
+ if (digest_alg == SEC_OID_SHA1 || digest_alg == SEC_OID_MD5)
+ {
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+ }
+
+ ctx = PK11_CreateDigestContext(digest_alg);
+ if (!ctx)
+ elog(ERROR, "out of memory");
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ elog(ERROR, "out of memory");
+ digest.len = digest_len;
+
+ status = SECSuccess;
+ status |= PK11_DigestBegin(ctx);
+ status |= PK11_DigestOp(ctx, certificate->derCert.data, certificate->derCert.len);
+ status |= PK11_DigestFinal(ctx, digest.data, &outlen, digest_len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+ elog(ERROR, "could not generate server certificate hash");
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+ PORT_FreeArena(arena, PR_TRUE);
+ PK11_DestroyContext(ctx, PR_TRUE);
+
+ return ret;
+}
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might as
+ * well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+/*
+ * pg_bad_cert_handler
+ *
+ * Callback for handling certificate validation failure during handshake. It's
+ * called from SSL_AuthCertificate during failure cases.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+/*
+ * tag_to_name
+ *
+ * Return the name of the RDN identified by the tag passed as parameter. NSS
+ * has this information in a struct, but the only publicly accessible API for
+ * using it is limited to retrieving the maximum length of the ava.
+ */
+static const char *
+tag_to_name(SECOidTag tag)
+{
+ switch(tag)
+ {
+ case SEC_OID_AVA_COMMON_NAME:
+ return "CN";
+ case SEC_OID_AVA_STATE_OR_PROVINCE:
+ return "ST";
+ case SEC_OID_AVA_ORGANIZATION_NAME:
+ return "O";
+ case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME:
+ return "OU";
+ case SEC_OID_AVA_COUNTRY_NAME:
+ return "C";
+ case SEC_OID_AVA_LOCALITY:
+ return "L";
+ case SEC_OID_AVA_SURNAME:
+ return "SN";
+ case SEC_OID_AVA_DC:
+ return "DC";
+ case SEC_OID_RFC1274_MAIL:
+ return "MAIL";
+ case SEC_OID_RFC1274_UID:
+ return "UID";
+ /*
+ * TODO: what would be the most appropriate thing to do here?
+ */
+ default:
+ return "";
+ }
+}
+
+/*
+ * pg_CERT_GetDistinguishedName
+ *
+ * NSS doesn't provide a DN equivalent of CERT_GetCommonName, so we have to
+ * keep our own.
+ */
+static char *
+pg_CERT_GetDistinguishedName(CERTCertificate *cert)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+ StringInfoData dnbuf;
+ char *dn = NULL;
+
+ initStringInfo(&dnbuf);
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+ bool first = true;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ SECStatus status;
+ char *tmpstr;
+ char *dststr;
+ int tmplen;
+
+ /* See raw_subject_common_name comment for discussion */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ return NULL;
+
+ tmpstr = palloc0(buf->len + 1);
+ tmplen = buf->len;
+ memcpy(tmpstr, buf->data, buf->len);
+ SECITEM_FreeItem(buf, PR_TRUE);
+ /* Check for embedded null */
+ if (strlen(tmpstr) != tmplen)
+ return NULL;
+
+ /* Make room for the worst case escaping */
+ dststr = palloc0((tmplen * 3) + 1);
+
+ /*
+ * While the function name refers to RFC 1485, the underlying
+ * quoting code conforms to the RFC 2253 rules.
+ */
+ status = CERT_RFC1485_EscapeAndQuote(dststr, tmplen * 3, tmpstr, tmplen);
+ if (status != SECSuccess)
+ {
+ pfree(dststr);
+ pfree(tmpstr);
+ return NULL;
+ }
+
+ appendStringInfo(&dnbuf, "%s%s=%s",
+ (first ? "" : ","),
+ tag_to_name(CERT_GetAVATag(*ava)), dststr);
+ first = false;
+
+ pfree(dststr);
+ pfree(tmpstr);
+ }
+ }
+
+ if (dnbuf.len)
+ {
+ dn = MemoryContextAllocZero(TopMemoryContext, dnbuf.len + 1);
+ strlcpy(dn, dnbuf.data, dnbuf.len + 1);
+ }
+
+ return dn;
+}
+
+/*
+ * raw_subject_common_name
+ *
+ * Returns the Subject Common Name for the given certificate as a raw char
+ * buffer (that is, without any form of escaping for unprintable characters or
+ * embedded nulls), with the length of the buffer returned in the len param.
+ * The buffer is allocated in the TopMemoryContext and is given a NULL
+ * terminator so that callers are safe to call strlen() on it.
+ *
+ * This is used instead of CERT_GetCommonName(), which always performs quoting
+ * and/or escaping. NSS doesn't appear to give us a way to easily unescape the
+ * result, and we need to store the raw CN into port->peer_cn for compatibility
+ * with the OpenSSL implementation.
+ */
+static char *
+raw_subject_common_name(CERTCertificate *cert, unsigned int *len)
+{
+ CERTName subject = cert->subject;
+ CERTRDN **rdn;
+
+ for (rdn = subject.rdns; *rdn; rdn++)
+ {
+ CERTAVA **ava;
+
+ for (ava = (*rdn)->avas; *ava; ava++)
+ {
+ SECItem *buf;
+ char *cn;
+
+ if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME)
+ continue;
+
+ /* Found a CN, decode and copy it into a newly allocated buffer */
+ buf = CERT_DecodeAVAValue(&(*ava)->value);
+ if (!buf)
+ {
+ /*
+ * This failure case is difficult to test. (Since this code
+ * runs after certificate authentication has otherwise
+ * succeeded, you'd need to convince a CA implementation to
+ * sign a corrupted certificate in order to get here. Writing
+ * a specialized CA for testing purposes would be required for
+ * this, which would make the testcase possible.)
+ *
+ * Follow the behavior of CERT_GetCommonName() in this case and
+ * simply return NULL, as if a Common Name had not been found.
+ */
+ goto fail;
+ }
+
+ cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1);
+ memcpy(cn, buf->data, buf->len);
+ cn[buf->len] = '\0';
+
+ *len = buf->len;
+
+ SECITEM_FreeItem(buf, PR_TRUE);
+ return cn;
+ }
+ }
+
+fail:
+ /* Not found */
+ *len = 0;
+ return NULL;
+}
+
+/*
+ * pg_cert_auth_handler
+ *
+ * Callback for validation of incoming certificates. Returning SECFailure will
+ * cause NSS to terminate the connection immediately.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ unsigned int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status != SECSuccess)
+ return status;
+
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+
+ cert = SSL_PeerCertificate(port->pr_fd);
+ if (!cert)
+ {
+ /* Shouldn't be possible; why did we get a client cert callback? */
+ Assert(cert != NULL);
+
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ peer_cn = raw_subject_common_name(cert, &len);
+ if (!peer_cn)
+ {
+ /* No Common Name, but the certificate otherwise checks out. */
+ port->peer_cert_valid = true;
+
+ status = SECSuccess;
+ goto cleanup;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent attacks like
+ * CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+
+ port->peer_dn = pg_CERT_GetDistinguishedName(cert);
+ if (!port->peer_dn)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("unable to get SSL certificate's distinguished name")));
+
+ pfree(peer_cn);
+ PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0);
+
+ status = SECFailure;
+ goto cleanup;
+ }
+
+ status = SECSuccess;
+
+cleanup:
+ CERT_DestroyCertificate(cert);
+ return status;
+}
+
+static PRStatus
+pg_ssl_close(PRFileDesc *fd)
+{
+ /*
+ * Disconnect our private Port from the fd before closing out the stack.
+ * (Debug builds of NSPR will assert if we do not.)
+ */
+ fd->secret = NULL;
+ return PR_GetDefaultIOMethods()->close(fd);
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.close = pg_ssl_close;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ return layer;
+}
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
+
+/*
+ * pg_SSLAlertSent
+ * Callback for TLS alerts sent from NSS
+ *
+ * The alert system is intended to provide information on ongoing issues to
+ * the TLS implementation such that they can be logged. It is not replacing
+ * error handling for the NSS API, but rather adds a more finegrained way of
+ * extracting information on errors during connection processing. The actual
+ * error names are however only accessible in a private NSS header and not
+ * exported. Thus, we only use it for close_notify which is defined as zero.
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=956866 for further
+ * discussion on this interface.
+ */
+static void
+pg_SSLAlertSent(const PRFileDesc *fd, void *private_data, const SSLAlert *alert)
+{
+ Port *port = (Port *) private_data;
+
+ /*
+ * close_notify is not an error, but an indication that the connection
+ * was orderly closed. Since the backend is only closing connections where
+ * TLS is no longer in use, log an error in case this wasn't the case.
+ * close_notify is defined in the NSS private enum SSL3AlertDescription
+ * as zero.
+ */
+ if (alert->description == 0)
+ {
+ if (port->ssl_in_use)
+ ereport(COMMERROR,
+ errmsg("closing an active TLS connection"));
+ return;
+ }
+
+ /*
+ * Any other alert received indicates an error, but since they can't be
+ * made heads or tails with without reading the NSS source code let's
+ * just log them as DEBUG information as they are of limited value in
+ * any other circumstance.
+ */
+ ereport(DEBUG2,
+ (errmsg_internal("TLS alert received: %d", alert->description)));
+}
+
+/*
+ * pg_configure_ssl_ciphers
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies in NSS based on export
+ * (and import) restrictions: Domestic, Export and France. These are long since
+ * removed with all ciphers enabled by default. For 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.
+ */
+static bool
+pg_configure_ssl_ciphers(PRFileDesc *model)
+{
+ SECStatus status;
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ ereport(COMMERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return false;
+ }
+ }
+ else
+ {
+ char *ciphers;
+ char *c;
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+ bool found = false;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ if (pg_find_cipher(c, &ciphercode))
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ found = true;
+ if (status != SECSuccess)
+ {
+ pfree(ciphers);
+ ereport(COMMERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ return false;
+ }
+ }
+ }
+
+ pfree(ciphers);
+
+ if (!found)
+ {
+ ereport(COMMERROR,
+ (errmsg("no cipher-suites found")));
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index a05f67afb5..1cd1a724d0 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -50,6 +50,7 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+char *ssl_database;
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4c94f09c64..9f0e935820 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4452,7 +4452,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4520,6 +4524,16 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4548,8 +4562,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/cipher_nss.c b/src/common/cipher_nss.c
new file mode 100644
index 0000000000..a529aa7b9b
--- /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/pg_nss.h"
+
+#define INVALID_CIPHER 0xFFFF
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithm;
+
+static const NSSSignatureAlgorithm NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+bool
+pg_find_cipher(char *name, PRUint16 *cipher)
+{
+ const NSSCiphers *cipher_list;
+
+ for (cipher_list = NSS_CipherList; cipher_list->name; cipher_list++)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ {
+ *cipher = cipher_list->number;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pg_find_signature_algorithm(SECOidTag signature, SECOidTag *algorithm, int *len)
+{
+ const NSSSignatureAlgorithm *candidate;
+
+ candidate = NSS_SCRAMDigestAlgorithm;
+
+ for (candidate = NSS_SCRAMDigestAlgorithm; candidate->signature; candidate++)
+ {
+ if (signature == candidate->signature)
+ {
+ *algorithm = candidate->hash;
+ *len = candidate->len;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/common/protocol_nss.c b/src/common/protocol_nss.c
new file mode 100644
index 0000000000..783f5298ba
--- /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/pg_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 "TLSv1.0";
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+ }
+
+ return "unknown";
+}
+
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..0fde181e84
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_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/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMMON_PG_NSS_H
+#define COMMON_PG_NSS_H
+
+#ifdef USE_NSS
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr.h>
+#include <prerror.h>
+#include <prio.h>
+#include <prmem.h>
+#include <prtypes.h>
+
+
+#include <nss.h>
+#include <hasht.h>
+#include <secoidt.h>
+#include <sslproto.h>
+
+/* src/common/cipher_nss.c */
+bool pg_find_cipher(char *name, PRUint16 *cipher);
+bool pg_find_signature_algorithm(SECOidTag signature, SECOidTag *digest, int *len);
+
+/* src/common/protocol_nss.c */
+char *ssl_protocol_version_to_string(int version);
+
+#endif /* USE_NSS */
+
+#endif /* COMMON_PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index dd3e5efba3..d102bd56b5 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -212,13 +212,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with SSL enabled.)
+ * TLS backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -301,7 +306,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -311,6 +316,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index f0786e08b4..e37b1b67eb 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -98,6 +98,7 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+extern char *ssl_database;
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d2e3e3a57..c2a0694669 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -202,7 +202,7 @@
* USE_SSL code should be compiled only when compiling with an SSL
* implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a6a1db3356..220af6afa0 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -344,6 +344,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"ssldatabase", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, ssldatabase)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..bb1c514bcd
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1170 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/cryptohash.h"
+#include "common/pg_nss.h"
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+#include <ssl.h>
+#include <pk11func.h>
+#include <secerr.h>
+#include <secoidt.h>
+#include <secmod.h>
+
+static SECStatus pg_load_nss_module(SECMODModule **module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_param_to_nss(const char *protocol);
+static bool certificate_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/*
+ * This logic exists in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
+{
+ if (do_ssl)
+ {
+ conn->nss_context = NULL;
+ conn->ca_trust = NULL;
+
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+ }
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ /*
+ * If the system trust module has been loaded we must try to unload it
+ * before closing the context, since it will otherwise fail reporting a
+ * SEC_ERROR_BUSY error.
+ */
+ if (conn->ca_trust != NULL)
+ {
+ if (SECMOD_UnloadUserModule(conn->ca_trust) != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to unload trust module");
+ }
+ else
+ {
+ SECMOD_DestroyModule(conn->ca_trust);
+ conn->ca_trust = NULL;
+ }
+ }
+
+ /*
+ * All NSS references must be cleaned up before we close out the
+ * context.
+ */
+ if (conn->pr_fd)
+ {
+ PRStatus status;
+
+ status = PR_Close(conn->pr_fd);
+ if (status != PR_SUCCESS)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to close NSPR fd: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->pr_fd = NULL;
+ }
+
+ if (conn->nss_context)
+ {
+ SECStatus status;
+
+ /* The session cache must be cleared, or we'll leak references */
+ SSL_ClearSessionCache();
+
+ status = NSS_ShutdownContext(conn->nss_context);
+
+ if (status != SECSuccess)
+ {
+ pqInternalNotice(&conn->noticeHooks,
+ "unable to shut down NSS context: %s",
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ conn->nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* This locking is modelled after fe-secure-openssl.c */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop while another thread owns the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to lock thread"));
+ return PGRES_POLLING_FAILED;
+ }
+#endif /* ENABLE_THREAD_SAFETY */
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+ params.dbTokenDescription = strdup("postgres");
+
+ if (conn->ssldatabase && strlen(conn->ssldatabase) > 0)
+ {
+ PQExpBufferData ssldatabase_path;
+
+ initPQExpBuffer(&ssldatabase_path);
+ appendPQExpBuffer(&ssldatabase_path, "sql:%s", conn->ssldatabase);
+
+ if (PQExpBufferDataBroken(ssldatabase_path))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->nss_context = NSS_InitContext(ssldatabase_path.data, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ termPQExpBuffer(&ssldatabase_path);
+
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->ssldatabase,
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ conn->nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_PK11RELOAD);
+ if (!conn->nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initialize NSS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify-*.
+ */
+ if (!conn->ssldatabase &&
+ (strcmp(conn->sslmode, "verify-ca") == 0 ||
+ strcmp(conn->sslmode, "verify-full") == 0))
+ {
+ status = pg_load_nss_module(&conn->ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load system NSS trust module \"%s\", create a local NSS database and retry : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!certificate_database_has_CA(conn))
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate database does not contain a CA certificate\n"
+ "Either provide a database with a CA or change sslmode to disable server certificate verification.\n"));
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy by setting the domestic suite.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ pthread_mutex_unlock(&ssl_config_mutex);
+ return PGRES_POLLING_FAILED;
+ }
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ conn->pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_param_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return PGRES_POLLING_FAILED;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_param_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return PGRES_POLLING_FAILED;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, conn->pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now be closed as we've applied the settings of the model
+ * onto the real socket. From here on we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Set callback for client authentication when requested by the server */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to for the ClientHello
+ * SNI extension, unless the user has disabled it. NSS will suppress IP
+ * addresses in SNIs, so we don't need to filter those explicitly like we do
+ * for OpenSSL.
+ */
+ if (conn->sslsni && conn->sslsni[0] == '1')
+ {
+ const char *host = conn->connhost[conn->whichhost].host;
+ if (host && host[0])
+ SSL_SetURL(conn->pr_fd, host);
+ }
+
+ status = SSL_ForceHandshake(conn->pr_fd);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ Assert(conn->nss_context);
+ Assert(conn->pr_fd);
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("TLS read error: EOF detected\n"));
+ read_errno = ECONNRESET;
+ nread = -1;
+ }
+ else if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_IO_TIMEOUT_ERROR:
+ /* No data available yet. */
+ nread = 0;
+ break;
+
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ if (nread == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read
+ *
+ * SSL_DataPending will check for decrypted data in the receiving buffer, but
+ * does not reveal anything about still encrypted data which will be made
+ * available. Thus, if pgtls_read_pending returns zero it does not guarantee
+ * that a subsequent call to pgtls_read would block. This is modelled
+ * around how the OpenSSL implementation treats pending data. The equivalent
+ * to the OpenSSL SSL_has_pending function would be to call PR_Recv with no
+ * wait and PR_MSG_PEEK like so:
+ *
+ * PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_DataPending(conn->pr_fd) > 0;
+}
+
+/*
+ * pgtls_write
+ * Write data on the secure socket
+ *
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_tag;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret = NULL;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ goto cleanup;
+
+ signature_tag = SECOID_GetAlgorithmTag(&server_cert->signature);
+ if (!pg_find_signature_algorithm(signature_tag, &digest_alg, &digest_len))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not find digest for OID '%s'\n"),
+ SECOID_FindOIDTagDescription(signature_tag));
+ goto cleanup;
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ if (!digest.data)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ goto cleanup;
+ }
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto cleanup;
+ }
+
+ ret = malloc(digest.len);
+ if (ret == NULL)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto cleanup;
+ }
+
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+
+cleanup:
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ if (server_cert)
+ CERT_DestroyCertificate(server_cert);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+
+ /*
+ * In the "standard" NSS use case, we'd call SSL_RevealURL() to get the
+ * hostname. In our case, SSL_SetURL() won't have been called if the user
+ * has disabled SNI, so we always pull the hostname from connhost instead.
+ */
+ server_hostname = conn->connhost[conn->whichhost].host;
+ if (!server_hostname || server_hostname[0] == '\0')
+ {
+ /* verify-full shouldn't progress this far without a hostname. */
+ return 0;
+ }
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname"));
+ return 0;
+ }
+
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ CERT_DestroyCertificate(server_cert);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return 0;
+ }
+
+ return 1;
+}
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ /*
+ * Try to get the user friendly error description, and if that fails try
+ * to fall back on the name of the PRErrorCode.
+ */
+ error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT);
+ if (!error)
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule **module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ PQExpBufferData modulespec;
+
+ initPQExpBuffer(&modulespec);
+ appendPQExpBuffer(&modulespec, "library=\"%s\", name=\"%s\"", library, name);
+ if (PQExpBufferDataBroken(modulespec))
+ {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec.data, NULL, PR_FALSE);
+ termPQExpBuffer(&modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ /*
+ * VerifyCertificateNow verifies the validity using PR_now from NSPR as a
+ * timestamp and will perform CRL and OSCP revocation checks. conn->sslcrl
+ * does not impact NSS connections as any CRL in the NSS database will be
+ * used automatically.
+ */
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc *socket, CERTDistNames *caNames,
+ CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+ char *nnptr = NULL;
+ char nickname[MAXPGPATH];
+
+ /*
+ * If we have an sslcert configured we use that combined with the token-
+ * name as the nickname. When no sslcert is set we pass in NULL and let
+ * NSS try to find the certificate among the ones present in the database.
+ * Without an sslcert we cannot set the database token since NSS parses
+ * the given string expecting to find a certificate nickname after the
+ * colon.
+ */
+ if (conn->sslcert)
+ {
+ snprintf(nickname, sizeof(nickname), "postgres:%s", conn->sslcert);
+ nnptr = nickname;
+ }
+
+ return NSS_GetClientAuthData(nnptr, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc *fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've set the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->ssldatabase &&
+ strlen(conn->ssldatabase) > 0 &&
+ certificate_database_has_CA(conn))
+ goto failure;
+ }
+ else if (strcmp(conn->sslmode, "verify-full") == 0 ||
+ strcmp(conn->sslmode, "verify-ca") == 0)
+ goto failure;
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we don't want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ break;
+ default:
+ goto failure;
+ break;
+ }
+
+ return SECSuccess;
+
+failure:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(err));
+
+ return SECFailure;
+}
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without even checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+/*
+ * ssl_protocol_param_to_nss
+ *
+ * Return the NSS internal representation of the protocol asked for by the
+ * user as a connection parameter.
+ */
+static int
+ssl_protocol_param_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+/*
+ * certificate_database_has_CA
+ * Check for the presence of a CA certificate
+ *
+ * Returns true in case there is a CA certificate in the database connected
+ * to in the conn object, else false. This function only checks for the
+ * presence of a CA certificate, not its validity and/or usefulness.
+ */
+static bool
+certificate_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * PQssl_passwd_cb
+ * Supply a password to decrypt an object
+ *
+ * If an object in the NSS database is password protected, or if the entire
+ * NSS database is itself password protected, this callback will be invoked.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * PQdefaultSSLKeyPassHook_nss
+ * Default password handler callback
+ *
+ * If no user defined password callback has been set up, the callback below
+ * will try the password set by the sslpassword parameter. Since we by legacy
+ * only have support for a single password, there is little reason to inspect
+ * the slot for the object in question. A TODO is to support different
+ * passwords for different objects, either via a DSL in sslpassword or with a
+ * new key/value style parameter. Users can supply their own password hook to
+ * do this of course, but it would be nice to support something in core too.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 0b998e254d..b7dcf570af 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -447,6 +447,27 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
}
#endif /* USE_OPENSSL */
+#ifndef USE_NSS
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return NULL;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ return;
+}
+
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return NULL;
+}
+#endif /* USE_NSS */
+
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 20eb855abc..119947871b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -665,6 +665,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4290553482..77d540716a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -81,6 +81,11 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_NSS
+#include "common/pg_nss.h"
+#include <secmod.h>
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -385,6 +390,7 @@ struct pg_conn
char *sslcrl; /* certificate revocation list filename */
char *sslcrldir; /* certificate revocation list directory name */
char *sslsni; /* use SSL SNI extension (0 or 1) */
+ char *ssldatabase; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -526,6 +532,29 @@ struct pg_conn
* removed as this locking is handled
* internally in OpenSSL >= 1.1.0. */
#endif /* USE_OPENSSL */
+
+/*
+ * The NSS/NSPR specific types aren't used to avoid pulling in the required
+ * headers here, as they are causing conflicts with PG definitions.
+ */
+#ifdef USE_NSS
+ NSSInitContext *nss_context; /* NSS connection specific context */
+ PRFileDesc *pr_fd; /* NSPR file descriptor for the connection */
+
+ SECMODModule *ca_trust;
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is
+ * that there are calls which require a password parameter, but doesn't
+ * use the callbacks provided, so we must call the callback on behalf of
+ * these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -787,7 +816,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
--
2.24.3 (Apple Git-128)
Hi,
On 2022-01-26 21:39:16 +0100, Daniel Gustafsson wrote:
What about
reconfiguring (note: add --enable-depend) the linux tasks to build against
nss, and then run the relevant subset of tests with it? Most tests don't use
tcp / SSL anyway, so rerunning a small subset of tests should be feasible?That's an interesting idea, I think that could work and be reasonably readable
at the same time (and won't require in-depth knowledge of Cirrus). As it's the
same task it does spend more time towards the max runtime per task, but that's
not a problem for now. It's worth keeping in mind though if we deem this to be
a way forward with testing multiple settings.
I think it's a way for a limited number of settings, that each only require a
limited amount of tests... Rerunning all tests etc is a different story.
Is it a great idea to have common/nss.h when there's a library header nss.h?
Perhaps we should have a pg_ssl_{nss,openssl}.h or such?That's a good point, I modelled it after common/openssl.h but I agree it's
better to differentiate the filenames. I've renamed it to common/pg_nss.h and
we should IMO rename common/openssl.h regardless of what happens to this patch.
+1
Does this make some things notably more expensive? Presumably it does remove a
bunch of COW opportunities, but likely that's not a huge factor compared to
assymetric crypto negotiation...Right, the context of setting up crypto across a network connection it's highly
likely to drown out the costs.
If you start to need to run a helper to decrypt an encrypted private key, and
do all the initialization, I'm not sure sure that holds true anymore... Have
you done any connection speed tests? pgbench -C is helpful for that.
Maybe soem of this commentary should migrate to the file header or such?
Maybe, or perhaps README.ssl? Not sure where it would be most reasonable to
keep it such that it's also kept up to date.
Either would work for me.
This introduce + * differences with the OpenSSL support where some errors are only reported + * at runtime with NSS where they are reported at startup with OpenSSL.Found this sentence hard to parse somehow.
It seems pretty unfriendly to only have minimal error checking at postmaster
startup time. Seems at least the presence and usability of keys should be done
*also* at that time?I'll look at adding some setup, and subsequent teardown, of NSS at startup
during which we could do checking to be more on par with how the OpenSSL
backend will report errors.
Cool.
+/* + * raw_subject_common_name + * + * Returns the Subject Common Name for the given certificate as a raw char + * buffer (that is, without any form of escaping for unprintable characters or + * embedded nulls), with the length of the buffer returned in the len param. + * The buffer is allocated in the TopMemoryContext and is given a NULL + * terminator so that callers are safe to call strlen() on it. + * + * This is used instead of CERT_GetCommonName(), which always performs quoting + * and/or escaping. NSS doesn't appear to give us a way to easily unescape the + * result, and we need to store the raw CN into port->peer_cn for compatibility + * with the OpenSSL implementation. + */Do we have a testcase for embedded NULLs in common names?
We don't, neither for OpenSSL or NSS. AFAICR Jacob spent days trying to get a
certificate generation to include an embedded NULL byte but in the end gave up.
We would have to write our own tools for generating certificates to add that
(which may or may not be a bad idea, but it hasn't been done).
Hah, that's interesting.
+/* + * pg_SSLShutdownFunc + * Callback for NSS shutdown + * + * If NSS is terminated from the outside when the connection is still in useWhat does "NSS is terminated from the outside when the connection" really
mean? Does this mean the client initiating something?If an extension, or other server-loaded code, interfered with NSS and managed
to close contexts in order to interfere with connections this would ensure us
closing it down cleanly.That being said, I was now unable to get my old testcase working so I've for
now removed this callback from the patch until I can work out if we can make
proper use of it. AFAICS other mature NSS implementations aren't using it
(OpenLDAP did in the past but have since removed it, will look at how/why).
I think that'd be elog(FATAL) time if we want to do anything (after changing
state so that no data is sent to client).
+ /* + * 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. + */Can we propose a patch to document them? Don't want to get bitten by this
suddenly changing...I can certainly propose something on their mailinglist, but I unfortunately
wouldn't get my hopes up too high as NSS and documentation aren't exactly best
friends (the in-tree docs doesn't cover the API and Mozilla recently removed
most of the online docs in their neverending developer site reorg).
Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.
In order to find a good split I think we need to figure what to optimize for;
do we optimize for ease of reverting should that be needed, or along
functionality borders, or something else? I don't have good ideas here, but a
single 7596 insertions(+), 421 deletions(-) commit is clearly not a good idea.
I think the goal should be the ability to incrementally commit.
Stephen had an idea off-list that we could look at splitting this across the
server/client boundary, which I think is the only idea I've so far which has
legs. (The first to go in would come with the common code of course.)
Yea, that's the most obvious one. I suspect client-side has a lower
complexity, because it doesn't need to replace quite as many things?
The attached v53 incorporates the fixes discussed above, and builds green for
both OpenSSL and NSS in Cirrus on my Github repo (thanks again for your work on
those files) so it will be interesting to see the CFBot running them.
Looks like that worked...
Greetings,
Andres Freund
On Wed, 2022-01-26 at 15:59 -0800, Andres Freund wrote:
Do we have a testcase for embedded NULLs in common names?
We don't, neither for OpenSSL or NSS. AFAICR Jacob spent days trying to get a
certificate generation to include an embedded NULL byte but in the end gave up.
We would have to write our own tools for generating certificates to add that
(which may or may not be a bad idea, but it hasn't been done).Hah, that's interesting.
Yeah, OpenSSL just refused to do it, with any method I could find at
least. My personal test suite is using pyca/cryptography and psycopg2
to cover that case.
--Jacob
Can we propose a patch to document them? Don't want to get bitten by this
suddenly changing...I can certainly propose something on their mailinglist, but I unfortunately
wouldn't get my hopes up too high as NSS and documentation aren't exactly best
friends (the in-tree docs doesn't cover the API and Mozilla recently removed
most of the online docs in their neverending developer site reorg).Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.
Sadly, there is that. While this is not a new problem, Mozilla has been making
some very weird decisions around NSS governance as of late. Another data point
is the below thread from libcurl:
https://curl.se/mail/lib-2022-01/0120.html
--
Daniel Gustafsson https://vmware.com/
On Fri, Jan 28, 2022 at 9:08 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.
Yeah, OpenSSL is very poor, so being worse is not good.
Sadly, there is that. While this is not a new problem, Mozilla has been making
some very weird decisions around NSS governance as of late. Another data point
is the below thread from libcurl:
I would really, really like to have an alternative to OpenSSL for PG.
I don't know if this is the right thing, though. If other people are
dropping support for it, that's a pretty bad sign IMHO. Later in the
thread it says OpenLDAP have dropped support for it already as well.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 28 Jan 2022, at 15:30, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jan 28, 2022 at 9:08 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.Yeah, OpenSSL is very poor, so being worse is not good.
Some background on this for anyone interested: Mozilla removed the
documentation from the MDN website and the attempt at resurrecting it in the
tree (where it should've been all along </rant>) isn't making much progress.
Some more can be found in this post on the NSS mailinglist:
https://groups.google.com/a/mozilla.org/g/dev-tech-crypto/c/p0MO7030K4A/m/Mx5St_2sAwAJ
--
Daniel Gustafsson https://vmware.com/
On 28 Jan 2022, at 15:30, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jan 28, 2022 at 9:08 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.Yeah, OpenSSL is very poor, so being worse is not good.
Sadly, there is that. While this is not a new problem, Mozilla has been making
some very weird decisions around NSS governance as of late. Another data point
is the below thread from libcurl:I would really, really like to have an alternative to OpenSSL for PG.
I don't know if this is the right thing, though. If other people are
dropping support for it, that's a pretty bad sign IMHO. Later in the
thread it says OpenLDAP have dropped support for it already as well.
I'm counting this and Andres' comment as a -1 on the patchset, and given where
we are in the cycle I'm mark it rejected in the CF app shortly unless anyone
objects.
--
Daniel Gustafsson https://vmware.com/
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
On 28 Jan 2022, at 15:30, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jan 28, 2022 at 9:08 AM Daniel Gustafsson <daniel@yesql.se> wrote:Kinda makes me question the wisdom of starting to depend on NSS. When openssl
docs are vastly outshining a library's, that library really should start to
ask itself some hard questions.Yeah, OpenSSL is very poor, so being worse is not good.
Sadly, there is that. While this is not a new problem, Mozilla has been making
some very weird decisions around NSS governance as of late. Another data point
is the below thread from libcurl:I would really, really like to have an alternative to OpenSSL for PG.
I don't know if this is the right thing, though. If other people are
dropping support for it, that's a pretty bad sign IMHO. Later in the
thread it says OpenLDAP have dropped support for it already as well.I'm counting this and Andres' comment as a -1 on the patchset, and given where
we are in the cycle I'm mark it rejected in the CF app shortly unless anyone
objects.
I agree that it's concerning to hear that OpenLDAP dropped support for
NSS... though I don't seem to be able to find any information as to why
they decided to do so. NSS is clearly still supported and maintained
and they do seem to understand that they need to work on the
documentation situation and to get that fixed (the current issue seems
to be around NSS vs. NSPR and the migration off of MDN to the in-tree
documentation as Daniel mentioned, if I followed the discussion
correctly in the bug that was filed by the curl folks and was then
actively responded to by the NSS/NSPR folks), which seems to be the main
issue that's being raised about it by the curl folks and here.
I'm also very much a fan of having an alternative to OpenSSL and the
NSS/NSPR license fits well for us, unlike the alternatives to OpenSSL
used by other projects, such as GnuTLS (which is the alternative to
OpenSSL that OpenLDAP now has) or other libraries like wolfSSL.
Beyond the documentation issue, which I agree is a concern but also
seems to be actively realized as an issue by the NSS/NSPR folks, is
there some other reason that the curl folks are thinking of dropping
support for it? Or does anyone have insight into why OpenLDAP decided
to remove support?
Thanks,
Stephen
Hi,
On 2022-01-31 14:24:03 +0100, Daniel Gustafsson wrote:
On 28 Jan 2022, at 15:30, Robert Haas <robertmhaas@gmail.com> wrote:
I would really, really like to have an alternative to OpenSSL for PG.
I don't know if this is the right thing, though. If other people are
dropping support for it, that's a pretty bad sign IMHO. Later in the
thread it says OpenLDAP have dropped support for it already as well.I'm counting this and Andres' comment as a -1 on the patchset, and given where
we are in the cycle I'm mark it rejected in the CF app shortly unless anyone
objects.
I'd make mine more a -0.2 or so. I'm concerned about the lack of non-code
documentation and the state of code documentation. I'd like an openssl
alternative, although not as much as a few years ago - it seems that the state
of openssl has improved compared to most of the other implementations.
Greetings,
Andres Freund
On 31 Jan 2022, at 17:24, Stephen Frost <sfrost@snowman.net> wrote:
* Daniel Gustafsson (daniel@yesql.se) wrote:
I'm counting this and Andres' comment as a -1 on the patchset, and given where
we are in the cycle I'm mark it rejected in the CF app shortly unless anyone
objects.I agree that it's concerning to hear that OpenLDAP dropped support for
NSS... though I don't seem to be able to find any information as to why
they decided to do so.
I was also unable to do that. There is no information that I could see in
either the commit message, Bugzilla entry (#9207) or on the mailinglist.
Searching the web didn't yield anything either. I've reached out to hopefully
get a bit more information.
I'm also very much a fan of having an alternative to OpenSSL and the
NSS/NSPR license fits well for us, unlike the alternatives to OpenSSL
used by other projects, such as GnuTLS (which is the alternative to
OpenSSL that OpenLDAP now has) or other libraries like wolfSSL.
Short of platform specific (proprietary) libraries like Schannel and Secure
Transport, the alternatives are indeed slim.
Beyond the documentation issue, which I agree is a concern but also
seems to be actively realized as an issue by the NSS/NSPR folks,
It is, but it has also been an issue for years to be honest, getting the docs
up to scratch will require a very large effort.
is there some other reason that the curl folks are thinking of dropping support
for it?
It's also not really used anymore in conjunction with curl, with Red Hat no
longer shipping builds against it.
--
Daniel Gustafsson https://vmware.com/
On 31 Jan 2022, at 22:32, Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2022-01-31 14:24:03 +0100, Daniel Gustafsson wrote:
On 28 Jan 2022, at 15:30, Robert Haas <robertmhaas@gmail.com> wrote:
I would really, really like to have an alternative to OpenSSL for PG.
I don't know if this is the right thing, though. If other people are
dropping support for it, that's a pretty bad sign IMHO. Later in the
thread it says OpenLDAP have dropped support for it already as well.I'm counting this and Andres' comment as a -1 on the patchset, and given where
we are in the cycle I'm mark it rejected in the CF app shortly unless anyone
objects.I'd make mine more a -0.2 or so. I'm concerned about the lack of non-code
documentation and the state of code documentation. I'd like an openssl
alternative, although not as much as a few years ago - it seems that the state
of openssl has improved compared to most of the other implementations.
IMHO I think OpenSSL has improved over OpenSSL of the past - which is great to
see - but they have also diverged themselves into writing a full QUIC
implementation which *I personally think* is a distraction they don't need.
That being said, there aren't too many other options.
--
Daniel Gustafsson https://vmware.com/
On 31 Jan 2022, at 22:48, Daniel Gustafsson <daniel@yesql.se> wrote:
On 31 Jan 2022, at 17:24, Stephen Frost <sfrost@snowman.net> wrote:
I agree that it's concerning to hear that OpenLDAP dropped support for
NSS... though I don't seem to be able to find any information as to why
they decided to do so.I was also unable to do that. There is no information that I could see in
either the commit message, Bugzilla entry (#9207) or on the mailinglist.
Searching the web didn't yield anything either. I've reached out to hopefully
get a bit more information.
Support issues and Red Hat dropping OpenLDAP was cited [0]https://curl.se/mail/lib-2022-02/0000.html as the main drivers
for dropping NSS.
--
Daniel Gustafsson https://vmware.com/
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
On 31 Jan 2022, at 22:48, Daniel Gustafsson <daniel@yesql.se> wrote:
On 31 Jan 2022, at 17:24, Stephen Frost <sfrost@snowman.net> wrote:
I agree that it's concerning to hear that OpenLDAP dropped support for
NSS... though I don't seem to be able to find any information as to why
they decided to do so.I was also unable to do that. There is no information that I could see in
either the commit message, Bugzilla entry (#9207) or on the mailinglist.
Searching the web didn't yield anything either. I've reached out to hopefully
get a bit more information.Support issues and Red Hat dropping OpenLDAP was cited [0] as the main drivers
for dropping NSS.
That's both very vaugue and oddly specific, I have to say. Also, not
really sure that it's a good reason for other projects to move away, or
for the large amount of work put into this effort to be thrown out when
it seems to be quite close to finally being done and giving us an
alternative, supported and maintained, TLS/SSL library.
The concern about the documentation not being easily available is
certainly something to consider. I remember in prior reviews not having
that much difficulty looking up documentation for functions, and in
doing some quick looking around there's certainly some (most?) of the
NSS documentation still up, the issue is that the NSPR documentation was
taken off of the MDN website and that's referenced from the NSS pages
and is obviously something that folks working with NSS need to be able
to find the documentation for too.
All that said, while have documentation on the web is nice and all, it
seems to still be in the source, at least when I grabbed NSPR locally
with apt-get source and looked at PR_Recv, I found:
/*
*************************************************************************
* FUNCTION: PR_Recv
* DESCRIPTION:
* Receive a specified number of bytes from a connected socket.
* The operation will block until some positive number of bytes are
* transferred, a time out has occurred, or there is an error.
* No more than 'amount' bytes will be transferred.
* INPUTS:
* PRFileDesc *fd
* points to a PRFileDesc object representing a socket.
* void *buf
* pointer to a buffer to hold the data received.
* PRInt32 amount
* the size of 'buf' (in bytes)
* PRIntn flags
* must be zero or PR_MSG_PEEK.
* PRIntervalTime timeout
* Time limit for completion of the receive operation.
* OUTPUTS:
* None
* RETURN: PRInt32
* a positive number indicates the number of bytes actually received.
* 0 means the network connection is closed.
* -1 indicates a failure. The reason for the failure is obtained
* by calling PR_GetError().
**************************************************************************
*/
So, it's not the case that the documentation is completely gone and
utterly unavailable to those who are interested in it, it's just in the
source rather than being on a nicely formatted webpage. One can find it
on the web too, naturally:
(no idea what version that is, just found a random github repo with it,
but wouldn't be hard to import the latest version).
Considering how much we point people to our source when they're writing
extensions and such, this doesn't strike me as quite the dire situation
that it first appeared to be based on the initial comments. There is
documentation, it's not actually that hard to find if you're working
with the library, and the maintainers have stated their intention to
work on improving the web-based documentation.
Thanks,
Stephen
Hi,
On 2022-02-01 15:12:28 -0500, Stephen Frost wrote:
The concern about the documentation not being easily available is
certainly something to consider. I remember in prior reviews not having
that much difficulty looking up documentation for functions
I've definitely several times in the course of this thread asked for
documentation about specific bits and there was none. And not just recently.
All that said, while have documentation on the web is nice and all, it
seems to still be in the source, at least when I grabbed NSPR locally
with apt-get source and looked at PR_Recv, I found:
What I'm most concerned about is less the way individual functions work, and
more a bit higher level things. Like e.g. about not being allowed to
fork. Which has significant design implications given postgres' process
model...
I think some documentation has been re-uploaded in the last few days. I recall
the content around https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS
being gone too, last time I checked.
So, it's not the case that the documentation is completely gone and
utterly unavailable to those who are interested in it, it's just in the
source rather than being on a nicely formatted webpage. One can find it
on the web too, naturally:
(no idea what version that is, just found a random github repo with it,
but wouldn't be hard to import the latest version).
It's last been updated 2015...
There's https://hg.mozilla.org/projects/nspr/file/tip/pr/src - which is I
think the upstream source.
A project without even a bare-minimal README at the root does have a "internal
only" feel to it...
Greetings,
Andres Freund
On Tue, Feb 1, 2022 at 01:52:09PM -0800, Andres Freund wrote:
There's https://hg.mozilla.org/projects/nspr/file/tip/pr/src - which is I
think the upstream source.A project without even a bare-minimal README at the root does have a "internal
only" feel to it...
I agree --- it is a library --- if they don't feel the need to publish
the API, it seems to mean they want to maintain the ability to change it
at any time, and therefore it is inappropriate for other software to
rely on that API.
This is not the same as Postgres extensions needing to read the Postgres
source code --- they are an important but edge use case and we never saw
the need to standardize or publish the internal functions that must be
studied and adjusted possibly for major releases.
This kind of feels like the Chrome JavaScript code that used to be able
to be build separately for PL/v8, but has gotten much harder to do in
the past few years.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
If only the physical world exists, free will is an illusion.
On 28.01.22 15:30, Robert Haas wrote:
I would really, really like to have an alternative to OpenSSL for PG.
What are the reasons people want that? With OpenSSL 3, the main reasons
-- license and FIPS support -- have gone away.
On 3 Feb 2022, at 15:07, Peter Eisentraut <peter.eisentraut@enterprisedb.com> wrote:
On 28.01.22 15:30, Robert Haas wrote:
I would really, really like to have an alternative to OpenSSL for PG.
What are the reasons people want that? With OpenSSL 3, the main reasons -- license and FIPS support -- have gone away.
At least it will go away when OpenSSL 3 is FIPS certified, which is yet to
happen (submitted, not processed).
I see quite a few valid reasons to want an alternative, a few off the top of my
head include:
- Using trust stores like Keychain on macOS with Secure Transport. There is
AFAIK something similar on Windows and NSS has it's certificate databases.
Especially on client side libpq it would be quite nice to integrate with where
certificates already are rather than rely on files on disks.
- Not having to install OpenSSL, Schannel and Secure Transport would make life
easier for packagers.
- Simply having an alternative. The OpenSSL projects recent venture into
writing transport protocols have made a lot of people worried over their
bandwidth for fixing and supporting core features.
Just my $0.02, everyones mileage varies on these.
--
Daniel Gustafsson https://vmware.com/
Greetings,
* Bruce Momjian (bruce@momjian.us) wrote:
On Tue, Feb 1, 2022 at 01:52:09PM -0800, Andres Freund wrote:
There's https://hg.mozilla.org/projects/nspr/file/tip/pr/src - which is I
think the upstream source.A project without even a bare-minimal README at the root does have a "internal
only" feel to it...I agree --- it is a library --- if they don't feel the need to publish
the API, it seems to mean they want to maintain the ability to change it
at any time, and therefore it is inappropriate for other software to
rely on that API.
This is really not a reasonable representation of how this library has
been maintained historically nor is there any reason to think that their
policy regarding the API has changed recently. They do have a
documented API and that hasn't changed- it's just that it's not easily
available in web-page form any longer and that's due to something
independent of the library maintenance. They've also done a good job
with maintaining the API as one would expect from a library and so this
really isn't a reason to avoid using it. If there's actual specific
examples of the API not being well maintained and causing issues then
please point to them and we can discuss if that is a reason to consider
not depending on NSS/NSPR.
This is not the same as Postgres extensions needing to read the Postgres
source code --- they are an important but edge use case and we never saw
the need to standardize or publish the internal functions that must be
studied and adjusted possibly for major releases.
I agree that extensions and public libraries aren't entirely the same
but I don't think it's all that unreasonable for developers that are
using a library to look at the source code for that library when
developing against it, that's certainly something I've done for a
number of different libraries.
This kind of feels like the Chrome JavaScript code that used to be able
to be build separately for PL/v8, but has gotten much harder to do in
the past few years.
This isn't at all like that case, where the maintainers made a very
clear and intentional choice to make it quite difficult for packagers to
pull v8 out to package it. Nothing like that has happened with NSS and
there isn't any reason to think that it will based on what the
maintainers have said and what they've done across the many years that
NSS has been around.
Thanks,
Stephen
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
On 3 Feb 2022, at 15:07, Peter Eisentraut <peter.eisentraut@enterprisedb.com> wrote:
On 28.01.22 15:30, Robert Haas wrote:
I would really, really like to have an alternative to OpenSSL for PG.
What are the reasons people want that? With OpenSSL 3, the main reasons -- license and FIPS support -- have gone away.
At least it will go away when OpenSSL 3 is FIPS certified, which is yet to
happen (submitted, not processed).I see quite a few valid reasons to want an alternative, a few off the top of my
head include:- Using trust stores like Keychain on macOS with Secure Transport. There is
AFAIK something similar on Windows and NSS has it's certificate databases.
Especially on client side libpq it would be quite nice to integrate with where
certificates already are rather than rely on files on disks.- Not having to install OpenSSL, Schannel and Secure Transport would make life
easier for packagers.- Simply having an alternative. The OpenSSL projects recent venture into
writing transport protocols have made a lot of people worried over their
bandwidth for fixing and supporting core features.Just my $0.02, everyones mileage varies on these.
Yeah, agreed on all of these.
Thanks,
Stephen
On 03.02.22 15:53, Daniel Gustafsson wrote:
I see quite a few valid reasons to want an alternative, a few off the top of my
head include:- Using trust stores like Keychain on macOS with Secure Transport. There is
AFAIK something similar on Windows and NSS has it's certificate databases.
Especially on client side libpq it would be quite nice to integrate with where
certificates already are rather than rely on files on disks.- Not having to install OpenSSL, Schannel and Secure Transport would make life
easier for packagers.
Those are good reasons for Schannel and Secure Transport, less so for NSS.
- Simply having an alternative. The OpenSSL projects recent venture into
writing transport protocols have made a lot of people worried over their
bandwidth for fixing and supporting core features.
If we want simply an alternative, we had a GnuTLS variant almost done a
few years ago, but in the end people didn't want it enough. It seems to
be similar now.
On Thu, Feb 3, 2022 at 2:16 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
If we want simply an alternative, we had a GnuTLS variant almost done a
few years ago, but in the end people didn't want it enough. It seems to
be similar now.
Yeah. I think it's pretty clear that the only real downside of
committing support for GnuTLS or NSS or anything else is that we then
need to maintain that support (or eventually remove it). I don't
really see a problem if Daniel wants to commit this, set up a few
buildfarm animals, and fix stuff when it breaks. If he does that, I
don't see that we're losing anything. But, if he commits it in the
hope that other people are going to step up to do the maintenance
work, maybe that's not going to happen, or at least not without
grumbling. I'm not objecting to this being committed in the sense that
I don't ever want to see it in the tree, but I'm also not volunteering
to maintain it.
As a philosophical matter, I don't think it's great for us - or the
Internet in general - to be too dependent on OpenSSL. Software
monocultures are not great, and OpenSSL has near-constant security
updates and mediocre documentation. Now, maybe anything else we
support will end up having similar issues, or worse. But if we and
other projects are never willing to support anything but OpenSSL, then
there will never be viable alternatives to OpenSSL, because a library
that isn't actually used by the software you care about is of no use.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Feb 3, 2022 at 01:42:53PM -0500, Stephen Frost wrote:
Greetings,
* Bruce Momjian (bruce@momjian.us) wrote:
On Tue, Feb 1, 2022 at 01:52:09PM -0800, Andres Freund wrote:
There's https://hg.mozilla.org/projects/nspr/file/tip/pr/src - which is I
think the upstream source.A project without even a bare-minimal README at the root does have a "internal
only" feel to it...I agree --- it is a library --- if they don't feel the need to publish
the API, it seems to mean they want to maintain the ability to change it
at any time, and therefore it is inappropriate for other software to
rely on that API.This is really not a reasonable representation of how this library has
been maintained historically nor is there any reason to think that their
policy regarding the API has changed recently. They do have a
documented API and that hasn't changed- it's just that it's not easily
available in web-page form any longer and that's due to something
independent of the library maintenance. They've also done a good job
So they have always been bad at providing an API, not just now, or that
their web content disappeared and they haven't fixed it, for how long?
I guess that is better than the v8 case, but not much. Is posting web
content really that hard for them?
with maintaining the API as one would expect from a library and so this
really isn't a reason to avoid using it. If there's actual specific
examples of the API not being well maintained and causing issues then
please point to them and we can discuss if that is a reason to consider
not depending on NSS/NSPR.
I have no specifics.
This is not the same as Postgres extensions needing to read the Postgres
source code --- they are an important but edge use case and we never saw
the need to standardize or publish the internal functions that must be
studied and adjusted possibly for major releases.I agree that extensions and public libraries aren't entirely the same
but I don't think it's all that unreasonable for developers that are
using a library to look at the source code for that library when
developing against it, that's certainly something I've done for a
number of different libraries.
Wow, you have a much higher tolerance than I do. How do you even know
which functions are the public API if you have to look at the source
code?
This kind of feels like the Chrome JavaScript code that used to be able
to be build separately for PL/v8, but has gotten much harder to do in
the past few years.This isn't at all like that case, where the maintainers made a very
clear and intentional choice to make it quite difficult for packagers to
pull v8 out to package it. Nothing like that has happened with NSS and
there isn't any reason to think that it will based on what the
maintainers have said and what they've done across the many years that
NSS has been around.
As far as I know, the v8 developers didn't say anything, they just
started moving things around to make it easier for them and harder for
packagers --- and they didn't care.
I frankly think we need some public statement from the NSS developers
before moving forward --- there are just too many red flags here, and
once we support it, it will be hard to remove support for it.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
If only the physical world exists, free will is an illusion.
On Thu, Feb 3, 2022 at 02:33:37PM -0500, Robert Haas wrote:
As a philosophical matter, I don't think it's great for us - or the
Internet in general - to be too dependent on OpenSSL. Software
monocultures are not great, and OpenSSL has near-constant security
updates and mediocre documentation. Now, maybe anything else we
I don't think it is fair to be criticizing OpenSSL for its mediocre
documentation when the alternative being considered, NSS, has no public
documentation. Can the source-code-defined NSS documentation be
considered better than the mediocre OpenSSL public documentation?
For the record, I do like the idea of adding NSS, but I am concerned
about its long-term maintenance, we you explained.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
If only the physical world exists, free will is an illusion.
On Fri, Feb 4, 2022 at 1:22 PM Bruce Momjian <bruce@momjian.us> wrote:
On Thu, Feb 3, 2022 at 02:33:37PM -0500, Robert Haas wrote:
As a philosophical matter, I don't think it's great for us - or the
Internet in general - to be too dependent on OpenSSL. Software
monocultures are not great, and OpenSSL has near-constant security
updates and mediocre documentation. Now, maybe anything else weI don't think it is fair to be criticizing OpenSSL for its mediocre
documentation when the alternative being considered, NSS, has no public
documentation. Can the source-code-defined NSS documentation be
considered better than the mediocre OpenSSL public documentation?
I mean, I think it's fair to say that my experiences with trying to
use the OpenSSL documentation have been poor. Admittedly it's been a
few years now so maybe it's gotten better, but my experience was what
it was. In one case, the function I needed wasn't documented at all,
and I had to read the C code, which was weirdly-formatted and had no
comments. That wasn't fun, and knowing that NSS could be an even worse
experience doesn't retroactively turn that into a good one.
For the record, I do like the idea of adding NSS, but I am concerned
about its long-term maintenance, we you explained.
It sounds like we come down in about the same place here, in the end.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Feb 4, 2022 at 01:33:00PM -0500, Robert Haas wrote:
I don't think it is fair to be criticizing OpenSSL for its mediocre
documentation when the alternative being considered, NSS, has no public
documentation. Can the source-code-defined NSS documentation be
considered better than the mediocre OpenSSL public documentation?I mean, I think it's fair to say that my experiences with trying to
use the OpenSSL documentation have been poor. Admittedly it's been a
few years now so maybe it's gotten better, but my experience was what
it was. In one case, the function I needed wasn't documented at all,
and I had to read the C code, which was weirdly-formatted and had no
comments. That wasn't fun, and knowing that NSS could be an even worse
experience doesn't retroactively turn that into a good one.
Oh, yeah, the OpenSSL documentation is verifiably mediocre.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com
If only the physical world exists, free will is an illusion.
On 4 Feb 2022, at 19:22, Bruce Momjian <bruce@momjian.us> wrote:
On Thu, Feb 3, 2022 at 02:33:37PM -0500, Robert Haas wrote:
As a philosophical matter, I don't think it's great for us - or the
Internet in general - to be too dependent on OpenSSL. Software
monocultures are not great, and OpenSSL has near-constant security
updates and mediocre documentation. Now, maybe anything else weI don't think it is fair to be criticizing OpenSSL for its mediocre
documentation when the alternative being considered, NSS, has no public
documentation. Can the source-code-defined NSS documentation..
Not that it will shift the needle either way, but to give credit where credit
is due:
Both NSS and NSPR are documented, and have been since they were published by
Netscape in 1998. The documentation does lack things, and some parts are quite
out of date. That's true and undisputed even by the projects themselves who
state this: "It currently is very deprecated and likely incorrect or broken in
many places".
The recent issue was that Mozilla decided to remove all 3rd party projects (why
they consider their own code 3rd party is a mystery to me) from their MDN site,
and so NSS and NSPR were deleted with no replacement. This was said to be
worked on but didn't happen and no docs were imported into the tree. When
Daniel from curl (the other one, not I) complained, this caused enough momentum
to get this work going and it's now been "done".
NSS: https://firefox-source-docs.mozilla.org/security/nss/
NSPR: https://firefox-source-docs.mozilla.org/nspr/
I am writing done above in quotes, since the documentation also needs to be
updated, completed, rewritten, organized etc etc. The above is an import of
what was found, and is in a fairly poor state. Unfortunately, it's still not
in the tree where I personally believe documentation stands the best chance of
being kept up to date. The NSPR documentation is probably the best of the two,
but it's also much less of a moving target.
It is true that the documentation is poor and currently in bad shape with lots
of broken links and heavily disorganized etc. It's also true that I managed to
implement full libpq support without any crystal ball or help from the NSS
folks. The latter doesn't mean we can brush documentation concerns aside, but
let's be fair in our criticism.
..be considered better than the mediocre OpenSSL public documentation?
OpenSSL has gotten a lot better in recent years, it's still not great or where
I would like it to be, but a lot better.
--
Daniel Gustafsson https://vmware.com/
Greetings,
* Bruce Momjian (bruce@momjian.us) wrote:
On Thu, Feb 3, 2022 at 01:42:53PM -0500, Stephen Frost wrote:
* Bruce Momjian (bruce@momjian.us) wrote:
On Tue, Feb 1, 2022 at 01:52:09PM -0800, Andres Freund wrote:
There's https://hg.mozilla.org/projects/nspr/file/tip/pr/src - which is I
think the upstream source.A project without even a bare-minimal README at the root does have a "internal
only" feel to it...I agree --- it is a library --- if they don't feel the need to publish
the API, it seems to mean they want to maintain the ability to change it
at any time, and therefore it is inappropriate for other software to
rely on that API.This is really not a reasonable representation of how this library has
been maintained historically nor is there any reason to think that their
policy regarding the API has changed recently. They do have a
documented API and that hasn't changed- it's just that it's not easily
available in web-page form any longer and that's due to something
independent of the library maintenance. They've also done a good jobSo they have always been bad at providing an API, not just now, or that
their web content disappeared and they haven't fixed it, for how long?
I guess that is better than the v8 case, but not much. Is posting web
content really that hard for them?
To be clear, *part* of the web-based documentation disappeared and
hasn't been replaced yet. The NSS-specific pieces are actually still
available, it's the NSPR (which is a lower level library used by NSS)
part that was removed from MDN and hasn't been brought back yet, but
which does still exist as comments in the source of the library.
with maintaining the API as one would expect from a library and so this
really isn't a reason to avoid using it. If there's actual specific
examples of the API not being well maintained and causing issues then
please point to them and we can discuss if that is a reason to consider
not depending on NSS/NSPR.I have no specifics.
Then I don't understand where the claim you made that "it seems to mean
they want to maintain the ability to change it at any time" has any
merit.
This is not the same as Postgres extensions needing to read the Postgres
source code --- they are an important but edge use case and we never saw
the need to standardize or publish the internal functions that must be
studied and adjusted possibly for major releases.I agree that extensions and public libraries aren't entirely the same
but I don't think it's all that unreasonable for developers that are
using a library to look at the source code for that library when
developing against it, that's certainly something I've done for a
number of different libraries.Wow, you have a much higher tolerance than I do. How do you even know
which functions are the public API if you have to look at the source
code?
Because... it's documented? They have public (and private) .h files in
the source tree and the function declarations have large comment blocks
above them which provide a documented API. I'm not talking about having
to decipher from the actual C code what's going on but just reading the
function header comment that provides the documentation of the API for
each of the functions, and there's larger blocks of comments at the top
of those .h files which provide more insight into how the functions in
that particular part of the system work and interact with each other.
Maybe those things would be better as separate README files like what we
do, but maybe not, and I don't see it as a huge failing that they chose
to use a big comment block at the top of their .h files to explain
things rather than separate README files.
Reading comments in code that I'm calling out to, even if it's in
another library (or another part of PG where the README isn't helping me
enough, or due to there not being a README for that particular thing)
almost seems typical, to me anyway. Perhaps the exception being when
there are good man pages.
I frankly think we need some public statement from the NSS developers
before moving forward --- there are just too many red flags here, and
once we support it, it will be hard to remove support for it.
They have made public statements regarding this and it's been linked to
already in this thread:
https://github.com/mdn/content/issues/12471
where they explicitly state that the project is alive and maintained,
further, it now now also links to this:
https://bugzilla.mozilla.org/show_bug.cgi?id=1753127
Which certainly seems to have had a fair bit of action taken on it.
Indeed, it looks like they've got a lot of the docs up and online now,
including the documentation for the function that started much of this:
https://firefox-source-docs.mozilla.org/nspr/reference/pr_recv.html#pr-recv
Looks like they're still working out some of the kinks between the NSS
pages and having links from them over to the NSPR pages, but a whole lot
of progress sure looks like it's been made in pretty short order here.
Definitely isn't looking unmaintained to me.
Thanks,
Stephen
Greetings,
* Bruce Momjian (bruce@momjian.us) wrote:
On Thu, Feb 3, 2022 at 02:33:37PM -0500, Robert Haas wrote:
As a philosophical matter, I don't think it's great for us - or the
Internet in general - to be too dependent on OpenSSL. Software
monocultures are not great, and OpenSSL has near-constant security
updates and mediocre documentation. Now, maybe anything else weI don't think it is fair to be criticizing OpenSSL for its mediocre
documentation when the alternative being considered, NSS, has no public
documentation. Can the source-code-defined NSS documentation be
considered better than the mediocre OpenSSL public documentation?
This simply isn't the case and wasn't even the case at the start of this
thread. The NSPR documentation was only available through the header
files due to it being taken down from MDN. The NSS documentation was
actually still there. Looks like they've now (mostly) fixed the lack of
NSPR documentation, as noted in the recent email that I sent.
For the record, I do like the idea of adding NSS, but I am concerned
about its long-term maintenance, we you explained.
They've come out and explicitly said that the project is active and
maintained, and they've been doing regular releases. I don't think
there's really any reason to think that it's not being maintained at
this point.
Thanks,
Stephen
Greetings,
* Daniel Gustafsson (daniel@yesql.se) wrote:
I am writing done above in quotes, since the documentation also needs to be
updated, completed, rewritten, organized etc etc. The above is an import of
what was found, and is in a fairly poor state. Unfortunately, it's still not
in the tree where I personally believe documentation stands the best chance of
being kept up to date. The NSPR documentation is probably the best of the two,
but it's also much less of a moving target.
I wonder about the 'not in tree' bit since it is in the header files,
certainly for NSPR which I've been poking at due to this discussion. I
had hoped that they were generating the documentation on the webpage
from what's in the header files, is that not the case then? Which is
more accurate? If it's a simple matter of spending time going through
what's in the tree and making sure what's online matches that, I suspect
we could find some folks with time to work on helping them there.
If the in-tree stuff isn't accurate then that's a bigger problem, of
course.
It is true that the documentation is poor and currently in bad shape with lots
of broken links and heavily disorganized etc. It's also true that I managed to
implement full libpq support without any crystal ball or help from the NSS
folks. The latter doesn't mean we can brush documentation concerns aside, but
let's be fair in our criticism.
Agreed.
Thanks,
Stephen
On 4 Feb 2022, at 21:03, Stephen Frost <sfrost@snowman.net> wrote:
I wonder about the 'not in tree' bit since it is in the header files,
certainly for NSPR which I've been poking at due to this discussion.
What I meant was that the documentation on the website isn't published from
documentation source code (in whichever format) residing in the tree.
That being said, I take that back since I just now in a git pull found that
they had done just that 6 days ago. It's just as messy and incomplete as what
is currently on the web, important API's like NSS_InitContext are still not
even mentioned more than in a release note, but I think it stands a better
chance of success than before.
I had hoped that they were generating the documentation on the webpage from
what's in the header files, is that not the case then?
Not from what I can tell no.
--
Daniel Gustafsson https://vmware.com/